UDF GraphQL resolver with nested type

I have this GraphQL schema:



And the implementation of the Function is:

And the indexes:
Board Index:
Columns Index (ordered by weight):
And the execution of the GraphQL query gives this error:

  1. Is there any documentation regarding how the structure of the response for a udf graphql resolver should look like in order to know what exactly is the correct way to implement this function?
  2. Are there any examples for this kind of udf graphql resolvers (with nested types)?
  3. If the answer is negative for 1) and 2), can you help me debugging/finding the fix for this function?
  4. How can udf graphql resolvers be debugged?

Thanks

Hi @ClaudiaGiv!

first, you want @index here. The docs provide such an example, so you should check that out.

In fact, fauna will automatically generate the index Query with just the following

type Query {
  boardsByTitle(title: String): [Board!]!
}

if you want to piggy back the existing index then call out @index(name: 'board_by_title')

But if you want to use a UDF, do not attempt to expand the related field. The strategy is to return just the an array of Documents. Or a whole Page. If you are not paginating, then your UDF would return something like Select('data', Paginate(...))

See also:

1 Like

To be clear again, any UDF that you use as a custom resolver should return the Documents of that type. Any relationships are resolved by the GraphQL API without additional work.

(it’s more complicated if @embedded types are used, but trying to keep it simple to start)

Lastly, for UDF debugging: debugging is hard. But search for “debug” here in the forums and you’ll find some discussion of ideas.

Thanks @ptpaterson for your replies. I looked over the doc links that you specified. My particular issue here is How can I return a single Board object (not an array) by the title, that contains also the list of Columns ordered by weight. If I would need only the Board (or array of boards) by title it would be as simple as using a custom index how you suggested, but ordering the columns inside the Board object is more complicated to deal with.
My general question is: How can I deal with these kind of implementations (or udf resolvers) where I have to return an object with an (ordered) list of nested objects inside?

Do you have access to the Board ID when making your query? I am not sure how you are getting the title to pass to your custom FQL function but Fauna autogenerates find<Collection>ByID graphql queries so if you are able to somehow access the ID as well as the title and then pass that to your findBoardByID graphql function then Fauna will handle the one-to-many relationship for you and allow you get a single Board with an array of Columns without you having to write any custom FQL.

As for ordering the Column list, I don’t think there is an easy way to handle that other than writing a custom FQL function as you have attempted already. I am still getting familiar with FQL so I am not sure if my attempt to write a function would work any better than yours but depending on if you have any access to the client at all, what I have done in this type of situation is used the autogenerated find<Collection>ByID graphql query, allowed Fauna to handle getting my one-to-many relation for me and then sorted the embedded list on the client.

I know that is not the best way to handle this but for me it was the easiest way and I just wanted to share in case it would help!

1 Like

Sorry for missing that you only want a single document

You can still do a UDF to return a single document. You would still not expand the related document. Only return the Document that is of the return type specified (a Board in this case)

You can Get the first result of a page directly with the Get function.

CreateFunction({
  name: 'GetBoardByTitle',
  role: null,
  body: Query(
    Lambda(
      ['title'],
      Get(Paginate(Match(Index('board_by_title'), Var('title'))))
    )
  )
})

Thanks @ptpaterson for keeping replying to this and for your patience. Yes, I understand how can I do in this case, for getting a single board based on title. My issue is that inside the board I want the columns to be ordered by weight. If I do it like you specified:

CreateFunction({
name: ‘GetBoardByTitle’,
role: null,
body: Query(
Lambda(
[‘title’],
Get(Paginate(Match(Index(‘board_by_title’), Var(‘title’))))
)
)
})

I will get the board by title also containing the columns, the columns are not ordered.
I understand that you also specified that I should not attempt to expand the board object inside the lambda and just return: Get(Paginate(Match(Index(‘board_by_title’), Var(‘title’)))). But in this case How can I order the columns inside the Board ?

@ClaudiaGiv My apologies again, you clearly said you needed Columns in order.

Unfortunately, that is not possible out-of-the-box. There are no ordering arguments possible in nested queries. There is a proposal put out there to make the generated API match the OpenCRUD specification, but that sounds a ways off.

(Note that Topic doesn’t address nested sorting directly, but OpenCRUD would)

I think you have a couple of options then to get the Columns data sorted.

Sort in your app

Are you paginating over the columns and need to only fetch the first 50 results of the lowest weight? Then sorting in the index will be important, and this won’t really be a good option.

If you will always fetch 100% of the columns for a given Board, then trying to sort on the Column weight inside the fauna query is probably much more of a burden than than the value you might get some it. Consider sorting the array in your app.

Query the columns directly

Instead of using a nested query, the UDF resolver can return the columns directly.

schema:

type Query {
  columnsByBoardTitle(title: String!): [Column!]! @resolver(name: 'columnsByBoardTitle')
}

UDF:

CreateFunction({
  name: 'columnsByBoardTitle',
  body: Query(
    Lambda(
      ['title'],
      Let(
        {
          boardRef: Select(
            ['data', 0],
            Paginate(Match(Index('unique_Board_title'), Var('title')))
          )
        },
        Select(
          'data',
          Map(
            Paginate(
              Match(
                Index('all_columns_by_board_order_by_weight'),
                Var('boardRef')
              ),
              { size: 100000 } // Max size, since not paginating
            ),
            Lambda(['weight', 'ref'], Get(Var('ref')))
          )
        )
      )
    )
  )
})

I got this working on my end:

Side Notes:

This will NOT be friendly to updating client-side caches, like in Apollo. The more canonical way to keep your boards and columns linked and cached properly is to run the nested query. Which means only doing the sort in your app.

This query shown provides no pagination for the columns, and return ALL of them (assuming under 100000 Documents). If you need to paginate, then you need to account for that in the UDF.

Using the @unique directive, there is already an index created automatically, and the default name would be unique_Board_title.

Create custom payload with @embedded

If you need both Board info and the columns info in one payload, then you can create a new @embedded type and return the data in that format.

1 Like