vkolgi / json-to-graphql-query

Simple module that converts JavaScript objects to GraphQL query syntax
MIT License
282 stars 39 forks source link

More Comprehensive Directive Support #7

Open dupski opened 6 years ago

dupski commented 6 years ago

There is a need to support Directives, currently just the @client directive for apollo, however I would like us to implement this in a generic way to allow for possible use of @skip(if: ...), etc. if needed.

Relavant GraphQL Spec: http://facebook.github.io/graphql/October2016/#sec-Directives-Are-In-Valid-Locations Medium article: https://medium.com/front-end-developers/graphql-directives-3dec6106c384

Initial Requirements:

Future Requirements

dupski commented 6 years ago

Possible way to implement:

Looking at the usage of @client from https://github.com/apollographql/apollo-link-state

How about something like this:

const query = {
  mutation: {
    updateNetworkStatus: {
      __args: {
        isConnected: true
      },
      __directives: {
        client: true
      }
    }
  }
};

This would then result in:

mutation {
    updateNetworkStatus(isConnected: true) @client
}

this should work for sub-nodes too, e.g.:

const query = {
  Post: {
    id: true,
    name: {
      __directives: {
        some_directive: true
      }
    }
  }
};

This would then result in:

query {
    Post {
      id
      name @some_directive
    }
}

It would be cool to support directive arguments too, e.g.:

const query = {
  Post: {
    id: true,
    name: {
      __directives: {
        myDirective: {
          directiveArg: 20
        }
      }
    }
  }
};
joeflack4 commented 6 years ago

Adding this support sounds useful. However, I'm wondering about support for the particular use case I have right now.

I have this object:

const everyDayHealthFocuses = {
    diet: {
        id: 'diet',
        options: {
            'calorie-count': {
                category: 'Diet',
                icon: 'fa fa-question-circle',
                id: 'calorie-count',
                selected: false,
                text: 'Calorie Count'
            },
            mood: {
                category: 'Diet',
                icon: 'fa fa-question-circle',
                id: 'mood',
                selected: false,
                text: 'Mood'
            },
            weight: {
                category: 'Diet',
                icon: 'fa fa-question-circle',
                id: 'weight',
                selected: false,
                text: 'Weight'
            },
        },
        title: 'Diet'
    },
};

I want a single function that I can call which will transform this object into a graphql query string, with the addition of the directive which instructs that this should be querying my local cache, e.g. @client:

query {
  diet @client {
    id
    options {
      'calorieCount' {
        category
        icon
        id
        selected
        text
      }
      mood {
        category
        icon
        id
        selected
        text
      }
      weight {
        category
        icon
        id
        selected
        text
      }
    }
    title
  }
}

As background, this object is an actual object being used in a React component in one of my applications.

As long as I can get this functionality, I'm happy.

I think that what you proposed can be in intermediate step between these two, e.g.:

const query = {
    diet: {
        __directives: {
          '@client': true
        }
        id: true,
        options: {
            'calorie-count': {
                category: true,
                icon: true,
                id: true,
                selected: true,
                text: true,
            },
            mood: {
                category: true,
                icon: true,
                id: true,
                selected: true,
                text: true,
            },
            weight: {
                category: true,
                icon: true,
                id: true,
                selected: true,
                text: true
            },
        },
        title: true
    },
};

Does something like this seem agreeable? I was going for an approach similar to this, but it seemed like more complex / longer code than simple string manipulation. But I think the string manipulation approach I was going for might not be quite as effective for a wider number of use cases.

Also I'm trying to think about the approach you're trying to go for with "directives" and "new WithDirectives()". I assume these are just ideas and you would be leaving the approach for how to finish adding the functionality to me? I've used ES6 classes for react, but never otherwise to combine with the "new" keyword to create an object in this instance. I'm not sure how I would implement this. You mentioned later that you think the "directives" approach would also work; I would also prefer that approach.

Before the string manipulation method, I was simply looking for the last key in my object, and then simply replacing its value with {apolloTarget: {'@client': true}}, "apolloTarget" just being arbitrarily chosen; I could name it something else of course. I didn't finish that approach completely, though.

On another aside, I'm still new with Apollo and was actually not building my query correctly before. I thought that I was supposed to add @client at the end of my query, as with mutations. But I think with queries, it looks like, if I'm correct, the only place I'd add @client would be between key names in the root of the query, and the next opening curly brace, e.g.:

query {
  key1 <could add a directive here> {
    ...
  }
  key2 <could also add a directive here> {
    ...
  }
  ...
}

On an aside, I wonder what the API for this functionality should be... Right now I have it as:

import { objToApolloQuery } from 'json-to-graphql-query';
...
objToApolloQuery(obj, target);
// ex: objToApolloQuery({a: {b: 'c'}}, 'client')

Since in my use case, my entire object will reside in the local cache, maybe export a dedicated function just for that? Ex: objToApolloLocalCacheQuery({a: b: 'c'}})

Idk, let me know if you also have a preference as to how I should expose this functionality to users.

dupski commented 6 years ago

Nice Joe

Yep your example above using __directives is exactly what I was thinking. I don't think we'll need the WithDirectives thing so please disregard that! :)

I now also don't think we should have a specific objToApolloQuery method in the library because Apollo is not doing anything non-standard.

All you will need in your own code is a method that does something like:

// get your "data" object, then...
data.diet['__directives`] = { client: true };
const query = jsonToGraphqlQuery(data, { keysToStrip: ['__typename'] });
joeflack4 commented 6 years ago

Hey Russell, Today I did a refactor of the code in PR #6, which includes support for directives, starting with arg-less ones, e.g. Apollo's "@client".

joeflack4 commented 6 years ago
dupski commented 6 years ago

Merged and released Joe's PR, but there are still a few open items:

PRs welcome as always! :)

vkolgi commented 2 years ago

Multiple directives on one node is kind of supported through this PR (https://github.com/vkolgi/json-to-graphql-query/pull/33) and merged.

joeflack4 commented 2 years ago

Thanks!