json-to-graphql-query icon indicating copy to clipboard operation
json-to-graphql-query copied to clipboard

More Comprehensive Directive Support

Open dupski opened this issue 6 years ago • 8 comments

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:

  • Directives can be added to any node in the tree
  • Multiple directives can be added for a single node

Future Requirements

  • Directives can have arguments

dupski avatar Jun 10 '18 05:06 dupski

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
        }
      }
    }
  }
};

dupski avatar Jun 10 '18 06:06 dupski

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.

joeflack4 avatar Jun 11 '18 03:06 joeflack4

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'] });

dupski avatar Jun 11 '18 06:06 dupski

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 avatar Jun 13 '18 01:06 joeflack4

  • Added support for queries that have arguments.
  • Added capability for nodes to have args and directives simultaneously.

joeflack4 avatar Jun 14 '18 23:06 joeflack4

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

  • We need to add support for multiple directives on one node
  • There are a few TODOs in the code and tests that need sorting
  • It would be good to support arguments for directives as per the graphQL spec.

PRs welcome as always! :)

dupski avatar Jul 02 '18 08:07 dupski

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

vkolgi avatar Dec 20 '21 08:12 vkolgi

Thanks!

joeflack4 avatar Dec 20 '21 21:12 joeflack4