Elegant way to add field to a page object

Hello,

So as I’m looking to integrate FQLv10 with a GraphQL gateway, one thing I’m noticing is that in order for the gateway to properly resolve union types it requires a __typename attribute to be available on the result that indicates which subtype it is. In the query, I am adding this manually on the resulting page object, but it’s a bit clunky. Is there a better way than creating a temporary struct and using Object.assign? As far as I can tell, a projection won’t work as that would add the __typename field to each resulting struct in data

An example for illustration:

Example GQL Schema
type Charge {
    id: ID
    chargeType: String
    chargeDate: Date
    """
    This amount is represented in the smallest currency unit,
    which for USD is cents.
    """
    chargeAmount: Int
}

"""
A paginated representation of a list of Charges.
"""
type ChargePage {
    data: [Charge]
    """
    The after cursor will be populated if there is more data after this page.
    """
    after: ID
}

"""
This indicates the error messages for a specific input field.
"""
type FieldError{
    field: String
    errors: [String]
}

"""
This indicates there was an error in the input provided to the mutation. The attributes
provide further information on the errors that were encountered.
"""
type InputError {
    fieldErrors: [FieldError]
    nonFieldErrors: [String]
}

"""
The possible results from a paginated Charge query. If the cursor provided is invalid,
then an InputError will be returned instead of the ChargePage.
"""
union ChargePageResult = ChargePage | InputError

type Account {
  id: ID
  charges(cursor: ID): ChargePageResult
}
Example FSL defining collections
collection Charge {
  index byAccount {
    terms [.account]
  }
}

collection Account {
}
Example FQLv10 Query
let account = Account.byId(${accountID})
let charges = Charge.byAccount(account) {
  id,
  chargeAmount
}
let chargeResult = {__typename: "ChargePage"}
account {
  id,
  charges: Object.assign(chargeResult, charges.paginate())
}

If you need to inject a field into the page result, then what you have done is appropriate.

That said, my initial instinct is that you should not try to explicitly define __typename in your FQL query. Instead, you can include the coll field in your document projections, and your resolver should be able to define the __typename based on the query you run and the coll field for documents (or whatever appropriate discriminator fields you have for an object).

In your example case, you need to resolve the Account.charges field, which could be valid results or an error. Your GraphQL resolver should be able to know the correct __typename by virtue of the query you are executing.

Using Fauna’s hosted GraphQL API as an example, with a database using the demo data:

If you execute a GraphQL query like this one:

{
  allCustomers {
    __typename
    data {
      __typename
      _id
      firstName
    }
  }
}

You get this result

{
  "data": {
    "allCustomers": {
      "__typename": "CustomerPage",
      "data": [
        {
          "__typename": "Customer",
          "_id": "101",
          "firstName": "Alice"
        },
        {
          "__typename": "Customer",
          "_id": "102",
          "firstName": "Bob"
        },
        {
          "__typename": "Customer",
          "_id": "103",
          "firstName": "Carol"
        }
      ]
    }
  }
}

Here is the compiled FQL query I grabbed from Query Logs. Note that __typename fields are not included. The GraphQL-server resolvers calculate the __typename

Let(
  [{ v0: Paginate(Match(Index("all_customers")), { size: 50 }) }],
  {
    allCustomers: If(Equals(Var("v0"), null), null, {
      data: Map(
        Select("data", Var("v0"), []),
        Lambda(
          "ref",
          Let([{ elem: Get(Var("ref")) }], {
            _id: Select("ref", Var("elem"), null),
            firstName: Select(
              ["data", "firstName"],
              Var("elem"),
              null
            ),
          })
        )
      ),
    }),
  }
)
1 Like

That makes sense. The col is the name of the containing collection, correct?

The coll field is not a string but the Collection “Module” as you might use it in FQL. You can call .toString() on it in FQL or pull out the string name in your driver.

In your example, Account and Charge are Collection modules, which have the byId and byAccount methods on them, respectively.

Here is a very contrived example demonstrating that.
image

1 Like

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.