Graphql-engine: Hasura/Actions - Support for nested object types

Created on 18 May 2020  路  12Comments  路  Source: hasura/graphql-engine

I want to build action for ElasticSearch API, it returns an array of product ids but I am not able to create a custom type that is nested.

type Product {
  id: bigint!
  name: String
}

type ElasticOutput {
  products: [Product!]!
  aggregations: jsonb
}
actions server

Most helpful comment

I think nested objects really need to be an option for action responses. Not only do you currently have to flatten all of your objects, but you also have to flatten errors into those same objects. If you really need error messages then this results in nearly all the fields of an object being nullable just to accommodate the possibility of error properties This is far from optimal. Example:

Say you want to use Plaid to get all of a user's accounts from their Plaid Items. You expect to return an array of accounts. However, it's possible that one or more items will be in an error state and the user will need to know that about that item specifically because they will need to re-link that item. We would still want to return all accounts (array of objects) and then all items with errors (array of objects or itemIds). This is not currently possible due to the lack of nested objects in Hasura actions. As such, only an array of "accounts" are returned with the only non-nullable property being the itemId. When an Item has an error, an "empty" account with an error property has to be returned. Here's the type for the account (in ReasonML) if it helps:

  type account = {
    itemId: string,
    /** this will be a plaidError stringified, since Hasura doesnt allow nested objects */
    error: option(string),
    accountId: string,
    mask: string,
    name: string,
    officialName: option(string),
    subtype: string,
    [@bs.as "type"]
    type_: string,
    verificationStatus: string,
    balancesCurrent: float,
    balancesAvailable: option(float),
    balancesLimit: option(float),
    balancesIsoCurrencyCode: option(string),
    balancesUnofficialCurrencyCode: option(string),
  };

Note that an additional workaround is to provide a JSON string as a field value (like the error field in the above example).

I'm happy to answer any questions about this use case. Thanks to the Hasura team for all of their hard work, truly amazing and groundbreaking software.

All 12 comments

+1 it should be very useful 馃憤

Copying @0x777 's comment on an internal discussion here for reference as we continue work on this. This is an important capability to add asap:

Let's assume the above types and say you have a top level field called
get_config:CloudConfig!``.

Now, a query such as this comes in:

query {
  get_config {
    enabled_console
    enabled_APIs
  }
}

graphql-engine calls the HTTP handler, what should it respond with (note that you are not requesting jwt_secret)? It can respond with all of the CloudConfig data including the JWT secret even though that field is not requested, graphql-engine will then only send the requested fields in the selection set from this response. Seems straight forward.

Now consider mutually recursive custom types:

type comment {
  parent_id Int
  children [comment]!
  author_id Int!
  content String!
}

and your action definition is as follows:
get_comments(thread_id: Int!): [comment!]!

You cannot write a handler for this as the response of the handler depends on how deep the selection is. To avoid these complications in the first iteration, we just said 'no nested object types' in the fields of object types.

@coco98 So, is there a workaround or something we can do about that? :confused:

@CloudPower97 The workaround for now is to flatten what you'd want from a nested action type!

Returning lists is already supported, so you can define an action that returns a list of elements. That element can in turn have relationships with other entities in Hasura and so that should work automatically :)

Example:

type mutation  createPosts(): [PostResult]

type PostResult {
  id: Int!
}

Now, there can be a relationship from PostResult.id to the Post table so that the mutation response returns all the data.

Does this mean there is currently no way to derive a mutation for something like insert_user? The only thing that returns in the top-level is non-scalers thus deriving fails. I understand how insert_user_one could be made to work using relationships but it seems like actions cannot derived from anything that returns lists or objects?

I think nested objects really need to be an option for action responses. Not only do you currently have to flatten all of your objects, but you also have to flatten errors into those same objects. If you really need error messages then this results in nearly all the fields of an object being nullable just to accommodate the possibility of error properties This is far from optimal. Example:

Say you want to use Plaid to get all of a user's accounts from their Plaid Items. You expect to return an array of accounts. However, it's possible that one or more items will be in an error state and the user will need to know that about that item specifically because they will need to re-link that item. We would still want to return all accounts (array of objects) and then all items with errors (array of objects or itemIds). This is not currently possible due to the lack of nested objects in Hasura actions. As such, only an array of "accounts" are returned with the only non-nullable property being the itemId. When an Item has an error, an "empty" account with an error property has to be returned. Here's the type for the account (in ReasonML) if it helps:

  type account = {
    itemId: string,
    /** this will be a plaidError stringified, since Hasura doesnt allow nested objects */
    error: option(string),
    accountId: string,
    mask: string,
    name: string,
    officialName: option(string),
    subtype: string,
    [@bs.as "type"]
    type_: string,
    verificationStatus: string,
    balancesCurrent: float,
    balancesAvailable: option(float),
    balancesLimit: option(float),
    balancesIsoCurrencyCode: option(string),
    balancesUnofficialCurrencyCode: option(string),
  };

Note that an additional workaround is to provide a JSON string as a field value (like the error field in the above example).

I'm happy to answer any questions about this use case. Thanks to the Hasura team for all of their hard work, truly amazing and groundbreaking software.

I would like to vote up for this feature. It would enable us to have generic action response types, that would contain possible error messages and payload data, being either collections or objects.

We needed nested custom types for quite an advanced input object. The object had possible array relationships that could be _upserted_ by the user. I would have loved to do this as an Action, but because of this limitation mentioned in this issue, we needed to look for alternatives.

As a workaround, we created a serverless graphql server using apollo-server-vercel a fork of apollo-server-lambda to create Mutation that supported our advanced inputs.

Nested custom types for inputs and responses would be super valuable and prevents us from implementing a custom graphql server to make this happen or flatten our inputs (which in some cases would be unreasonable).

I'd like to second this request as well! :)

@coco98 I think a better solution would be to disallow recursive object trees, rather than disallowing all nested objects. I could be wrong, but that should solve the issue in the short term while giving users much greater flexibility.

I'm resorting to making and calling the REST API end points directly for now until this is supported via Action.

@coco98 what about blocking only recursive objects, and allowing everything else?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rikinsk-zz picture rikinsk-zz  路  3Comments

leoalves picture leoalves  路  3Comments

egislook picture egislook  路  3Comments

marionschleifer picture marionschleifer  路  3Comments

tirumaraiselvan picture tirumaraiselvan  路  3Comments