Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Node #32

Closed
smeijer opened this issue Nov 2, 2019 · 27 comments
Closed

Support for Node #32

smeijer opened this issue Nov 2, 2019 · 27 comments

Comments

@smeijer
Copy link

smeijer commented Nov 2, 2019

Can this also be used for command-line apps? Or does it depend on react?

It looks quite awesome for fast prototyping and quering a graphql endpoint in a node repl.

@samdenty
Copy link
Owner

samdenty commented Nov 2, 2019

@smeijer Yep. You can simply follow the instructions over at https://gqless.netlify.com and skip the react bit

You should access the data twice, the first time to fetch it - the second time to get its value

It may be possible to make it work synchronously, but currently it's async

@smeijer smeijer changed the title Support for CLI apps?C Support for CLI apps? Nov 2, 2019
@samdenty samdenty changed the title Support for CLI apps? Support for Node Mar 21, 2020
@samdenty samdenty self-assigned this Mar 21, 2020
@adrianhunter
Copy link

@samdenty any chance to optionally have a client where you can simply await queries?

@samdenty
Copy link
Owner

@adrianhunter yep that's the goal

function test() {
  console.log(query.name)
}

await preload(test)
test() // => Bob

@adrianhunter
Copy link

cool, also just

await query.foo

?

@samdenty
Copy link
Owner

samdenty commented Mar 22, 2020

Released a new resolved helper in gqless@0.0.1-alpha.28.

You can pass a GraphQL accessor:

const title = await resolved(query.hn!.topStories[0]!.title)!;

console.log(title);

Passing a callback will keep executing the callback until everything's been fetched, and then resolve the final return value:

const data = await resolved(() => ({
  title: query.hn!.topStories[0]!.title,
  id: query.hn!.topStories[0]!.id
}))!;
console.log(data);

See a demo here:
https://codesandbox.io/s/little-frog-x9wdl

@natew
Copy link
Contributor

natew commented Mar 28, 2020

Documenting this would be huge. We’re looking at gqless because honestly Apollo is sort of appalling how verbose it is.

Being able to run queries independently is super important for global state stores and non React usage obviously.

Beyond that optimistic updates that “just work” would be a huge killer feature. I’d be happy to jump in and help with either feature.

@samdenty
Copy link
Owner

samdenty commented Mar 30, 2020

@natew Started updating the docs over the weekend. Should be done sometime next week #74
Gonna add more examples on optimistic updates / usage outside of React.

End-goal is to automatically turn your GraphQL client into a smart SDK. Reactive SDK's like discord.js are waaaay easier than making rest calls / GraphQL requests. But they just cost too much to implement to that degree - plus they have overfetching. Stuff which GraphQL can solve

optimistic updates that “just work” would be a huge killer feature

Yep totally agree, gonna be essentially mobx - but the data is bidirectionally synced with your GraphQL server.

@rhlsthrm
Copy link

rhlsthrm commented Apr 6, 2020

This is awesome! I'm having some trouble pulling out array items from the resolver function though. For example if I want to get all the top stories' titles:

const stories = await resolved(query.hn!.topStories.map(story => story.title));

This is not working. Any ideas on how to make it work?

(node:21264) UnhandledPromiseRejectionWarning: Error: [gqless] Indeterminate accessor! '' not equal to 'Query....

Ensure calls to getAccessor() *always* dereference data inside the call

GOOD : `getAccessor(user.name)`
BAD  : `getAccessor(name)`

@samdenty
Copy link
Owner

samdenty commented Apr 6, 2020

@rhlsthrm turn that argument into a callback

const stories = await resolved(() =>
  query.hn!.topStories.map(story => story.title)
);

@rhlsthrm
Copy link

rhlsthrm commented Apr 6, 2020

@samdenty that worked perfectly! Thank you for the assistance.

@samdenty
Copy link
Owner

samdenty commented Apr 6, 2020

You can also do this, albeit looks worse:

await resolved(() => {
  // Access the data we want to resolve
  query.hn!.topStories.forEach(story => story.title);
});

// Data we requested is now resolved
const stories = query.hn!.topStories.map(story => story.title);
console.log(stories);

@rhlsthrm
Copy link

rhlsthrm commented Apr 8, 2020

Another q @samdenty (sorry if this issue isn't the best place).

How would I use this same type of approach to run a where query? i.e.

const stories = await resolved(() =>
  query.hn!.topStories
    .map(story => {
      return { title: story.title, date: story.date };
    })
    .filter(story => story.date > YESTERDAY)
);

Edit, this actually works perfectly but I'm wondering if this is the "right" way to do it. Some more examples along these lines would be super helpful. Loving this tool though!

@lukejagodzinski
Copy link
Contributor

lukejagodzinski commented Apr 8, 2020

@rhlsthrm first you have to understand how the resolved function works. Its only job is to await for the query result. You can do whatever kind of manipulation there, however it's not a good idea to do so. To better understand that let's first think why in the example from @samdenty he used the forEach function there.

await resolved(() => {
  // Access the data we want to resolve
  query.hn!.topStories.forEach(story => story.title); // <- why forEach here?
});

The answer is that he had to inform gqless what data he needs. What he is doing here is telling it that he wants the title field for each story. That's it. He is not doing any manipulation with data here. He is not mapping over data etc.

Then in the next line after await resolved he is working with data returned from the GraphQL server. And here you can do whatever you want but you can only work with data that was fetched in the first step. So to your question, you would do it like that:

await resolved(() => {
  query.hn!.topStories.forEach(story => {
    story.title;
    story.date;
  });
});

const stories = query.hn!.topStories;
stories.filter(story => story.date > YESTERDAY);

Maybe you would also add map there to just omit the __typename field from each story but that's up to you and what you really need it for.

@rhlsthrm
Copy link

rhlsthrm commented Apr 9, 2020

@lukejagodzinski thanks for that. This helped my understanding about "informing gqless what you need. I found another query that actually worked and seems to me like the right way to do it:

const stories = await resolved(() =>
  query.hn!.topStories
    .where({
      read: false
    })
    .map(story => {
      return {
        title: story.title,
        read: story.read
      };
    })
);

Now I am informing the resolved query what my filter params need to be before mapping. Let me know what you think about this.

@lukejagodzinski
Copy link
Contributor

lukejagodzinski commented Apr 9, 2020

@rhlsthrm what is the where function? And what you did there is not correct. It should be (assuming that the where function is a filter function):

await resolved(() => {
  query.hn!.topStories.forEach(story => {
    story.title;
    story.read;
  });
});

const stories = query.hn!.topStories
  .filter(story => story.read === false)
  .map(story => {
    return {
      title: story.title,
      read: story.read
    };
  });

@rhlsthrm
Copy link

rhlsthrm commented Apr 9, 2020

The subgraph I am interacting with is using where, maybe it's just a property of that, although it behaves like filter. Why shouldn't the filter part be part of the resolved? My understanding from your earlier comment was that resolved tells gqless what data it needs, so you are querying before fetching all the results.

For example, if we look at this subgraph: https://lucasconstantino.github.io/graphiql-online/

And I want to run a query like this:

query {
  allUsers(filter: { name_starts_with: "A" }) {
    id
    name
  }
}

I would think I would query by writing:

const stories = await resolved(() =>
  query.allUsers
    .filter({
      name_starts_with: "A"
    })
    .map(user => {
      return {
        id: user.id,
        name: user.name
      };
    })
);

To me this seems "right", and the filter fields actually autocomplete from TypeScript from the generated code from the schema.

@lukejagodzinski
Copy link
Contributor

lukejagodzinski commented Apr 9, 2020

@rhlsthrm gqless is using proxies and react suspense, to make it feel like data is there even though it's not yet there. When you do:

await resolved(() => query.users);

You start building query, so until that point your query will look like:

query {
  users {
    __typename
  }
}

Now you want to specify what user properties you want to fetch:

await resolved(() => query.users.forEach(user => {
  user.firstName;
  user.lastName;
  user.posts;
}));

Just by "touching" (accessing) some properties you add it to the query. So now your query looks like:

query {
  users {
    __typename
    firstName
    lastName
    posts {
      __typename
    }
  }
}

You can move on and add more nested properties:

await resolved(() => query.users.forEach(user => {
  user.firstName;
  user.lastName;
  user.posts.forEach(post => {
    post.title;
    post.content;
  });
}));

Now, your query looks like:

query {
  users {
    __typename
    firstName
    lastName
    posts {
      __typename
      title
      content
    }
  }
}

In this process, you're just telling gqless what fields you need. That's it. At this point gqless will create a Promise and throw it (at least on the client, not sure about node). From my understanding similar process happens in the resolve() helper. It just catches such a promise and performs query. Now as it's already fetched you can move on with data and do some transformation after await resolve(). I'm not saying that data can't be transformed in the resolve() helper but it will make time to query slower. Any operation you perform there will slow down creating a query and data will be available at later time than you would expect. Limiting number of operations in resolve() makes building query faster. I don't know how I can say it in another words :). Hope it's now clearer for you :)

@lukejagodzinski
Copy link
Contributor

Actually if I understand it correctly you don't even need to do forEach, you could just do:

await resolved(() => {
  const user = query.users[0];
  user.firstName;
  user.lastName;
  const post = user.posts[0];
  post.title;
  post.content;
});

@rhlsthrm
Copy link

@lukejagodzinski thanks for all the help, I'm starting to understand more and more. The one thing your examples are leaving out is how to add the filter property to the query.

This is the query I want to generate:

query {
  allUsers(filter: { name_starts_with: "A" }) {
    id
    name
  }
}

I don't want to just fetch all the users, I want to fetch users based on a condition.

Actually if I understand it correctly you don't even need to do forEach

I don't understand how this could work. I want more than the [0] indexed user, I want all the users based on a condition.

@rhlsthrm
Copy link

rhlsthrm commented Apr 10, 2020

I just checked the generated query using what I posted earlier:

const stories = await resolved(() =>
  query.allUsers
    .filter({
      name_starts_with: "A"
    })
    .map(user => {
      return {
        id: user.id,
        name: user.name
      };
    })
);

The above code generates the following query:

query {
  allUsers(filter: { name_starts_with: "a" }) {
    id
    name
  }
}

This is exactly what I need, so I will go ahead and proceed with this. Thanks again for the help @lukejagodzinski.

@smeijer
Copy link
Author

smeijer commented Apr 10, 2020

@rhlsthrm, are you sure? Shouldn't the filter be a function argument to get that query?:

const stories = await resolved(() =>
  query
    .allUsers({
      filter: { name_starts_with: 'A' },
    })
    .map(user => ({
      id: user.id,
      name: user.name,
    }))
);

@samdenty, I guess we can close this issue now that we have resolved?

@rhlsthrm
Copy link

@smeijer you're totally right, this was a bad copy/paste on my part. Your syntax is perfect.

@lukejagodzinski
Copy link
Contributor

Oh filter is an argument of query Hehe so that's why all this confusion :-).

And about forEach, yes you don't need to run it. It's just about visiting at least one field from the object. Even if you visit element 0 it's enough for gqless to generate query that will fetch all records. As I said it's about just "touching" some field at least once to tell query builder what fields are we interested in

@rhlsthrm
Copy link

Even if you visit element 0 it's enough for gqless to generate query that will fetch all records.

Oh this is interesting! I will experiment with this.

@smeijer smeijer closed this as completed Apr 17, 2020
@smeijer
Copy link
Author

smeijer commented Apr 17, 2020

I've closed this one, as resolved covers the need I had when creating this issue.

@itsezc
Copy link

itsezc commented May 24, 2021

Is there any easy way to extract all fields w/o touching or an easy way to touch them all - preferably built into GQless, without having to build anything into resolved

@PabloSzx
Copy link
Contributor

Is there any easy way to extract all fields w/o touching or an easy way to touch them all - preferably built into GQless, without having to build anything into resolved

https://gqless.com/client/helper-functions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants