samdenty / gqless

a GraphQL client without queries
https://gqless.com
3.66k stars 54 forks source link

Mutations #51

Closed lukejagodzinski closed 3 years ago

lukejagodzinski commented 4 years ago

Hi! Thanks for the great package!

I was searching for information about how to perform mutations and I've just realized that it's work in progress. Any expected date of release? I would like to ditch Apollo Client :) and this library is one of the best things that happen for the client side GraphQL in a while.

lukejagodzinski commented 4 years ago

If you need any help with programming I might dedicate some of my free time to this project :)

Also for anyone looking for mutations support there is a way to use the fetch function from the generated code. Of course there will not be code autocompletion out of the box but at least you will be able use mutations.

Go to {outputDir}/client.ts and add export in front of the const fetchQuery

export const fetchQuery: QueryFetcher = async (query, variables) => {

Now, you can import and use it in your code like this:

import { fetchQuery } from "{outputDir}";

const SIGN_IN_MUTATION = `
  mutation SignIn($email: String!, $password: String!) {
    signIn(email: $email, password: $password) {
      token
      user {
        id
        fullName
      }
    }
  }
`;

const { data: { signIn } } = await fetchQuery(SIGN_IN_MUTATION, {
  email,
  password: sha256(password)
});

You could also type result but it's a little bit ugly this way:

import { fetchQuery, SignInResult } from "{outputDir}";

const SIGN_IN_MUTATION = `
  mutation SignIn($email: String!, $password: String!) {
    signIn(email: $email, password: $password) {
      token
      user {
        id
        fullName
      }
    }
  }
`;

const {
  data: { signIn }
}: { data: { signIn: SignInResult } } = await fetchQuery(SIGN_IN_MUTATION, {
  email,
  password: sha256(password)
});
samdenty commented 4 years ago

This is highly dependent on the GQLess compiler

The API I'm planning for (transformed using the compilers Babel plugin):

const data = await mutation.signIn({ email,  password })

console.log(data.token, data.user.fullName)
lukejagodzinski commented 4 years ago

Yeah the syntax is what I would expect it to be. How much time do you think it would take and what are steps required to implement it? Maybe people (including me) could help with that if there is a list of things to do.

It's funny, when I saw your library yesterday I was like "wow that's almost exactly what I wanted to create" :). Maybe the only difference would be no use of Proxies. But tbh I think that if you release stable version of this library, then in one year from now anyone starting a new project with GraphQL will use your library instead of Apollo. It's just a nail into Apollo's coffin 😜

lukejagodzinski commented 4 years ago

Also little update for someone who wants to use API similar to official one. Just create file called mutation.ts and fill it with content similar to this:

import { fetchQuery, Mutation } from "{outputDir}";
import { QueryResponse } from "gqless";

const mutations = {
  signIn: `
    mutation SignIn($email: String!, $password: String!) {
      signIn(email: $email, password: $password) {
        token
        user {
          id
          fullName
        }
      }
    }
  `
};

const signIn = async (variables: Parameters<Mutation["signIn"]>[0]) => {
  const result = await (fetchQuery(mutations["signIn"], variables) as Promise<
    QueryResponse<{ signIn: ReturnType<Mutation["signIn"]> }>
  >);
  return result.data.signIn;
};

export const mutation = {
  signIn
};

Wanted to create something more generic but TS didn't want to cooperate :)

kpfromer commented 4 years ago

This package is crazy cool! Regarding mutations, could there be a section on the documentation website written about using mutations (using the above method) temporarily before 1.0 is released? I can create a pull request to do that if that is something that you want to be done. Again, this is a fantastic package and agree with @lukejagodzinski about it being an Apollo killer.

PabloSzx commented 4 years ago

I was experimenting and I was able to make a mutation hook that uses all the type-safety already generated.

The problem is that TypeScript generics via hooks don't work as expected, and this should be added manually (or generated via gqless generate) in src/graphql/client.ts to work properly.

Here is the code that has to be added in the generated file.

And Here is the usage in React.

Looks like this

const [mutation, { state, data }] = useMutation(schema =>
      schema.mutateHelloWorld({
        asd: "zxc"
      })
    );

state is "waiting" | "loading" | "error" | "done", data the resulting data, and mutation is a function that returns a promise of the data

lukejagodzinski commented 4 years ago

@PabloSzx nicely done. I will definitely try that before official support. Thanks!

PabloSzx commented 4 years ago

@lukejagodzinski I just released a library that uses this logic, but much more refined https://github.com/PabloSzx/gqless-hooks

lukejagodzinski commented 4 years ago

@PabloSzx thanks I will probably use that but it would also be great if there is an official @gqless/hooks package. So maybe you guys could collaborate on that?

PabloSzx commented 4 years ago

Of course I'd like to help :smile: the thing is, there are already some pull requests on hold :eyes: and at first glance I think the design of these hooks are not the ideal design @samdenty was thinking about, since are not Suspense based :man_shrugging: .

lukejagodzinski commented 4 years ago

I think that using Suspense is not a bad thing even though it's not stable feature. If you're going to use gqless in the first place you're already using Proxies which are not supported by IE and there is no good polyfill for that. So in some sense you're already using feature that might be problematic. Also after releasing Suspense, the React team said that now it's time for the packages authors to test it out so they can move it to stable release. So someone has to do that job to make it official. If there is really a need to have non-Suspense based hooks there could be a separate package for that in deed.

veeral-patel commented 4 years ago

@samdenty Hey - thanks for this library from me as well! How would I update the cache after a mutation?

I don't care about optimistic updates myself, but I would like to update the cache with minimal boilerplate, in order to update the UI, based on the response from the mutation.

rhlsthrm commented 4 years ago

Really liking this tool, planning to go all in on it on my current project and ditch Apollo and all its boilerplate. Any update on the status of official support for mutations? In the meantime, I'll use these workarounds.

rhlsthrm commented 4 years ago

@lukejagodzinski I'm trying your strategy but running into some issues. I'm using Hasura to generate my GraphQL endpoint, and when I generate gqless, it seems to give me something called mutation_root instead of Mutation in the generated schema. I'm trying to copy your code:

import { fetchQuery, mutation_root } from "./index";
import { QueryResponse } from "gqless";

const mutations = {
  linkAccount: `
    mutation LinkAccount($publicToken: String!, $userId: uuid!) {
      linkAccount(accountData: { publicToken: $publicToken, userId: $userId }) {
        message
      }
    }
  `
};

const signIn = async (variables: Parameters<mutation_root["linkAccount"]>[0]) => {
  const result = await (fetchQuery(mutations["linkAccount"], variables) as Promise<
    QueryResponse<{ linkAccount: ReturnType<mutation_root["linkAccount"]> }>
  >);
  return result.data.linkAccount;
};

export const mutation = {
  signIn
};

However I'm getting a type error on the mutation_root["linkAccount"]:

Type 'null | undefined' does not satisfy the constraint '(...args: any) => any'.
Type 'undefined' is not assignable to type '(...args: any) => any'.ts(2344)

Any ideas on how to fix this?

samdenty commented 4 years ago

I've been super busy recently, so no ETA yet. Have been working on the compiler - which is a massive undertaking.

In theory I could get it to work for some basic use cases currently, but I still have a lot of work proving out how it works

natew commented 4 years ago

@rhlsthrm I'm using Hasura too but my src/generated/types.ts doesn't include mutation_root at all. It includes all the queries, and if I separately use graphql generator my schema.json shows all the mutations just fine.

PabloSzx commented 3 years ago

Hi @kpfromer, @veeral-patel & @rhlsthrm, We have just released the new version v2 of gqless, please visit https://gqless.com for instructions, it now has Mutation support, Subscriptions support, Normalization and much more, feel free to enter our discord server, to open an issue or a discussion about anything related, thanks you for your contribution! 🎉