vuex-orm / plugin-graphql

Vuex ORM persistence plugin to sync the store against a GraphQL API.
https://vuex-orm.github.io/plugin-graphql/
MIT License
227 stars 52 forks source link

get server schema/json instead of declare manually? #80

Open gotexis opened 5 years ago

gotexis commented 5 years ago

I saw this from the documentation - the plugin does know of the schema of the grapheQL server.

https://vuex-orm.github.io/plugin-graphql/guide/faq.html#does-vuex-orm-graphql-know-my-graphql-schema

Yes, it does! Before the first query is sent, the plugin loads the schema from the GraphQL server and extracts different information like the types of the fields to use, which fields to ignore, because they're not in the schema and whether custom queries and mutations return a connection or a record.

GraphQL servers usually generate a single schema.json for the app.

I wonder if there is a way to consume it, instead of declaring all models/fields manually, like I have read in vuex-orm-core documentation.

This may be too much to ask tho

phortx commented 5 years ago

GraphQL servers usually generate a single schema.json for the app.

Never heard of that. Sounds interesting. Currently the plugin runs a introspection query against the GraphQL API to get the schema.

I wonder if there is a way to consume it, instead of declaring all models/fields manually, like I have read in vuex-orm-core documentation.

I thought about that: Based on the GraphQL schema, we could auto generate the Vuex-ORM models. This isn't easy, but we could do some investigation on this in the future.

gotexis commented 5 years ago

@phortx You are right, based on my limited knowledge of GraphQL, I thought the OPTION introspection query on API was called "serving a cached JSON" .... lul So basically it was actually serving a cached JSON but I never thought the official way of calling it was "Introspection".

So regarding the boilerplate, because I was using Django-Graphene, I ended up writing a code generator that converts all models into VueX-ORM-GraphQL schema.

# jinja2 template
import {Model} from '@vuex-orm/core'

// import related models
// -------------------------------------------------------------------------------------
{% for model in related_models %}
import {{ model|name }} from 'models/{{ model|name }}'
{% endfor %}

// main
// -------------------------------------------------------------------------------------
export default class {{ model|name }} extends Model {

  static entity = '{{ model|name|lower }}'

  static fields() {
    return {
      {% for f in model_fields %}
        {% if f|type(fields.related.ManyToOneRel) %} {# pass #}

        {% elif f|type((fields.AutoField, fields.CharField, fields.TextField)) %}                     {# PK, Str #}
            {{ f.attname | __ }}: this.string(''),

        {% elif f|type(fields.IntegerField) %}                                                        {# int #}
            {{ f.attname | __ }}: this.int(''),

        {% elif f|type((fields.DateField, fields.TimeField, fields.DateTimeField)) %}                 {# datetime #}
            {{ f.attname | __ }}: this.string(''),    // defaulting to str as of now...

        {% elif f|type(fields.related.ForeignKey) %}                                                  {# FK #}

            {% with related_model = f.related_fields[0][1].model if f.related_fields else f._related_fields[0][1].model %}
                {% if related_model not in exclude_models and related_model|name not in exclude_model_names %}
            {{ f.attname|replace("_id", "   ") | __ }}: this.belongsTo({{ related_model|name }}, '{{ related_model|name|lower }}Id'),
                {% endif %}
            {% endwith %}

        {% elif f|type(fields.related.ManyToManyField) %}
            {% if f.related_model not in exclude_models and f.related_model|name not in exclude_model_names %}
        {#      as per https://vuex-orm.github.io/plugin-graphql/guide/relationships.html#many-to-many #}
            {{ f.attname | __ }}: this.belongsToMany({{ f.related_model|name }}, {{ f.remote_field.through|name }}, '{{ model|name|lower }}Id' , '{{ f.related_model|name|lower }}Id'),
            {% else %}
            {% endif %}
        {% endif %}

      {% endfor %}
    }
  }
}

converts to this:

import {Model} from '@vuex-orm/core'
// import related models
// -------------------------------------------------------------------------------------
import Shop from 'models/Shop'
import Order from 'models/Order'
import Cart from 'models/Cart'
import Item from 'models/Item'
// main
// -------------------------------------------------------------------------------------
export default class User extends Model {
  static entity = 'user'
  static fields() {
    return {
            id                            : this.string(''),
            password                      : this.string(''),
            last_login                    : this.string(''),    // defaulting to str as of now...
            username                      : this.string(''),
            first_name                    : this.string(''),
            last_name                     : this.string(''),
            email                         : this.string(''),
            date_joined                   : this.string(''),    // defaulting to str as of now...
            referer1                      : this.belongsTo(User, 'userId'),
            referer2                      : this.belongsTo(User, 'userId'),
            referer3                      : this.belongsTo(User, 'userId'),
            shop                          : this.belongsTo(Shop, 'shopId'),
            name                          : this.string(''),
            item                          : this.belongsToMany(Item, Cart, 'userId' , 'itemId'),
    }
  }
}

Surely I understand this isn't proper programming, but it helps me achieve a lot in a relatively short amount of time ;) Plus I love dark magic

phortx commented 5 years ago

This is a nice approach. I'm very open for the idea of initially generating the Vuex-ORM models via the introspection query. I think this would be a very useful feature.

emahuni commented 5 years ago

Hi. this sounds super interesting. I am wondering. I haven't read the plugin's source, but at a high level, isn't it that your plugin does sort of the opposite? as in convert vuex queries to graphQL. Can't the same resolution logic then be used to figure out the vuex models definitions from the introspection?

phortx commented 5 years ago

Yes exactly. We just have to implement it 😎