A "Component" pattern for GraphQL

Problem

The GraphQL API does not support Union types, yet you need query results that are comparable to using Unions.

This recipe is one solution to the problem outlined in Composable Types without Unions.

Solution

The following "component" pattern describes entities as being a single type built from various components, rather than as individual types. It is similar to the "sparse fields" introduced in the previous recipe: it uses a single Type for all entities, but rather than put additional fields at the top level, you wrap the entities' data into various @embedded types. This pattern provides more control over types and GraphQL queries.

Not all entities have the same properties: A movie does not have a number of pages like a book, and a book does not have a length of time like a movie. However, entities of different types can share some properties, such as whether they are "new releases" or are being "promoted" somehow. This pattern allows us to reuse the same component to describe multiple types, similar to how Union types would.

Setup

  1. Update your GraphQL schema to include:

    type User {
      name: String! @unique
      favorites: [MediaItem]! @relation
    }
    
    type MediaItem {
      title: String!
      favoritedBy: [User]! @relation
    
      components: Components
    }
    
    type Components @embedded {
      # components with data
      movie: Movie,
      show: Show,
      book: Book,
    
      # singleton components
      promoted: Boolean
      newRelease: Boolean
    }
    
    type Movie @embedded {
      length: Float!
      # ... other Movie fields
    }
    
    type Show @embedded {
      seasons: Int!
      # ... other Show fields
    }
    
    type Book @embedded {
      pages: Int!
      # ... other Book fields
    }
    
    type Query {
      itemsWithComponent(component: String!): [MediaItem]!
        @resolver(paginated: true)
      itemsWithAllComponents(components: [String!]!): [MediaItem]!
        @resolver(paginated: true)
      itemsWithAnyComponents(components: [String!]!): [MediaItem]!
        @resolver(paginated: true)
    }

    This involves editing the GraphQL schema definition, wherever you have stored it, and then uploading the new schema in the Fauna Dashboard.

  2. Create a new Index to support some custom resolvers.

    The following code sample includes variations for the supported Drivers, as well as a Shell example that you can copy+paste into the Shell tab in the Fauna Dashboard.

    try
    {
        Value result = await client.Query(
            CreateIndex(
                Obj(
                    "name", "MediaItem_has_component",
                    "source", Obj(
                        "collection", Collection("MediaItem"),
                        "fields", Obj(
                            "components", Query(
                                Lambda(
                                    "doc",
                                    Let(
                                        "components",
                                        Select(
                                            Arr("data", "components"),
                                            Var("doc"),
                                            Obj()
                                        ),
                                        "component_keys",
                                        Map(
                                            ToArray(Var("components")),
                                            Lambda(
                                                "c",
                                                Select(0, Var("c"))
                                            )
                                        )
                                    ).In(
                                        Var("component_keys")
                                    )
                                )
                            )
                        )
                    ),
                    "terms", Arr(
                        Obj("binding", "components")
                    )
                )
            )
        );
        Console.WriteLine(result);
    }
    catch (Exception e)
    {
        Console.WriteLine($"ERROR: {e.Message}");
    }
    ObjectV(ref: RefV(id = "MediaItem_has_component", collection = RefV(id = "indexes")),ts: LongV(1654791391840000),active: BooleanV(True),serialized: BooleanV(True),name: StringV(MediaItem_has_component),source: ObjectV(collection: RefV(id = "MediaItem", collection = RefV(id = "collections")),fields: ObjectV(components: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))),terms: Arr(ObjectV(binding: StringV(components))),partitions: LongV(1))
    The Go version of this example is not currently available.
    System.out.println(
        client.query(
            CreateIndex(
                Obj(
                    "name", Value("MediaItem_has_component"),
                    "source", Obj(
                        "collection", Collection("MediaItem"),
                        "fields", Obj(
                            "components", Query(
                                Lambda(
                                    "doc",
                                    Let(
                                        "components", Select(
                                            Arr(Value("data"), Value("components")),
                                            Var("doc"),
                                            Obj()
                                        ),
                                        "component_keys", Map(
                                            ToArray(Var("components")),
                                            Lambda("c", Select(Value(0), Var("c")))
                                        )
                                    ).in(
                                        Var("component_keys")
                                    )
                                )
                            )
                        )
                    ),
                    "terms", Arr(
                        Obj("binding", Value("components"))
                    )
                )
            )
        ).get());
    {ref: ref(id = "MediaItem_has_component", collection = ref(id = "indexes")), ts: 1654791419640000, active: true, serialized: true, name: "MediaItem_has_component", source: {collection: ref(id = "MediaItem", collection = ref(id = "collections")), fields: {components: QueryV({api_version=4, lambda=doc, expr={let={components={select=[data, components], from={var=doc}, default={object={}}}, component_keys={map={lambda=c, expr={select=0, from={var=c}}}, collection={to_array={var=components}}}}, in={var=component_keys}}})}}, terms: [{binding: "components"}], partitions: 1}
    client.query(
      q.CreateIndex({
        name: 'MediaItem_has_component',
        source: {
          collection: q.Collection('MediaItem'),
          fields: {
            components: q.Query(
              q.Lambda(
                'doc',
                q.Let(
                  {
                    components: q.Select(
                      ['data', 'components'],
                      q.Var('doc'),
                      {}
                    ),
                    component_keys: q.Map(
                      q.ToArray(q.Var('components')),
                      q.Lambda('c', q.Select(0, q.Var('c')))
                    ),
                  },
                  q.Var('component_keys')
                )
              )
            ),
          },
        },
        terms: [{ binding: 'components' }],
      })
    )
    .then((ret) => console.log(ret))
    .catch((err) => console.error(
      'Error: [%s] %s: %s',
      err.name,
      err.message,
      err.errors()[0].description,
    ))
    {
      ref: Index("MediaItem_has_component"),
      ts: 1654791433510000,
      active: true,
      serialized: true,
      name: 'MediaItem_has_component',
      source: {
        collection: Collection("MediaItem"),
        fields: {
          components: Query(Lambda("doc", Let({"components": Select(["data", "components"], Var("doc"), {}), "component_keys": Map(ToArray(Var("components")), Lambda("c", Select(0, Var("c"))))}, Var("component_keys"))))
        }
      },
      terms: [ { binding: 'components' } ],
      partitions: 1
    }
    result = client.query(
      q.create_index({
        "name": "MediaItem_has_component",
        "source": {
          "collection": q.collection("MediaItem"),
          "fields": {
            "components": q.query(
              q.lambda_(
                "doc",
                q.let(
                  {
                    "components": q.select(
                      ["data", "components"],
                      q.var("doc"),
                      {}
                    ),
                    "component_keys": q.map_(
                      q.lambda_("c", q.select(0, q.var("c"))),
                      q.to_array(q.var("components"))
                    ),
                  },
                  q.var("component_keys")
                )
              )
            ),
          },
        },
        "terms": [{ "binding": "components" }],
      })
    )
    print(result)
    {'ref': Ref(id=MediaItem_has_component, collection=Ref(id=indexes)), 'ts': 1654791436680000, 'active': True, 'serialized': True, 'name': 'MediaItem_has_component', 'source': {'collection': Ref(id=MediaItem, collection=Ref(id=collections)), 'fields': {'components': Query({'api_version': '4', 'lambda': 'doc', 'expr': {'let': {'components': {'select': ['data', 'components'], 'from': {'var': 'doc'}, 'default': {'object': {}}}, 'component_keys': {'map': {'lambda': 'c', 'expr': {'select': 0, 'from': {'var': 'c'}}}, 'collection': {'to_array': {'var': 'components'}}}}, 'in': {'var': 'component_keys'}}})}}, 'terms': [{'binding': 'components'}], 'partitions': 1}
    CreateIndex({
      name: "MediaItem_has_component",
      source: {
        collection: Collection("MediaItem"),
        fields: {
          components: Query(
            Lambda(
              "doc",
              Let(
                {
                  components: Select(
                    ["data", "components"],
                    Var("doc"),
                    {}
                  ),
                  component_keys: Map(
                    ToArray(Var("components")),
                    Lambda("c",  Select(0, Var("c")))
                  )
                },
                Var("component_keys")
              )
            )
          )
        }
      },
      terms: [
        { binding: "components" }
      ]
    })
    {
      ref: Index("MediaItem_has_component"),
      ts: 1654791439330000,
      active: true,
      serialized: true,
      name: 'MediaItem_has_component',
      source: {
        collection: Collection("MediaItem"),
        fields: {
          components: Query(Lambda("doc", Let({"components": Select(["data", "components"], Var("doc"), {}), "component_keys": Map(ToArray(Var("components")), Lambda("c", Select(0, Var("c"))))}, Var("component_keys"))))
        }
      },
      terms: [ { binding: 'components' } ],
      partitions: 1
    }
    Query metrics:
    •    bytesIn:   500

    •   bytesOut:   667

    • computeOps:     1

    •    readOps:     0

    •   writeOps:     1

    •  readBytes: 1,718

    • writeBytes:   692

    •  queryTime:  26ms

    •    retries:     0

    This index is required by the UDF resolvers defined in the following steps.

  3. Create a UDF to aid pagination with custom resolvers.

    Each of the resolver UDFs that we need to create involve pagination. Since the pagination logic is identical in each case, we can create a helper UDF that the resolver UDFs can call.

    The following code sample includes variations for the supported Drivers, as well as a Shell example that you can copy+paste into the Shell tab in the Fauna Dashboard.

    try
    {
        Value result = await client.Query(
            CreateFunction(
                Obj(
                    "name", "paginate_helper",
                    "body", Query(
                        Lambda(
                            Arr("match", "size", "after", "before"),
                            If(
                                Equals(Var("before"), null),
                                If(
                                    Equals(Var("after"), null),
                                    Paginate(
                                        Var("match"),
                                        size: Var("size")
                                    ),
                                    Paginate(
                                        Var("match"),
                                        size: Var("size"),
                                        after: Var("after")
                                    )
                                ),
                                Paginate(
                                    Var("match"),
                                    size: Var("size"),
                                    before: Var("before")
                                )
                            )
                        )
                    )
                )
            )
        );
        Console.WriteLine(result);
    }
    catch (Exception e)
    {
        Console.WriteLine($"ERROR: {e.Message}");
    }
    ObjectV(ref: RefV(id = "paginate_helper", collection = RefV(id = "functions")),ts: LongV(1654791395650000),name: StringV(paginate_helper),body: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))
    The Go version of this example is not currently available.
    System.out.println(
        client.query(
            CreateFunction(
                Obj(
                    "name", Value("paginate_helper"),
                    "body", Query(
                        Lambda(
                            Arr(
                                Value("match"),
                                Value("size"),
                                Value("after"),
                                Value("before")
                            ),
                            If(
                                Equals(Var("before"), null),
                                If(
                                    Equals(Var("after"), null),
                                    Paginate(Var("match"))
                                        .size(Var("size")),
                                    Paginate(Var("match"))
                                        .after(Var("after"))
                                        .size(Var("size"))
                                ),
                                Paginate(Var("match"))
                                    .before(Var("before"))
                                    .size(Var("size"))
                            )
                        )
                    )
                )
            )
        ).get());
    {ref: ref(id = "paginate_helper", collection = ref(id = "functions")), ts: 1654791431950000, name: "paginate_helper", body: QueryV({api_version=4, lambda=[match, size, after, before], expr={if={equals=[{var=before}, null]}, then={if={equals=[{var=after}, null]}, then={paginate={var=match}, size={var=size}}, else={paginate={var=match}, after={var=after}, size={var=size}}}, else={paginate={var=match}, before={var=before}, size={var=size}}}})}
    client.query(
      q.CreateFunction({
        name: 'paginate_helper',
        body: q.Query(
          q.Lambda(
            ['match', 'size', 'after', 'before'],
            q.If(
              q.Equals(q.Var('before'), null),
              q.If(
                q.Equals(q.Var('after'), null),
                q.Paginate(q.Var('match'), { size: q.Var('size') }),
                q.Paginate(q.Var('match'), { size: q.Var('size'), after: q.Var('after') })
              ),
              q.Paginate(q.Var('match'), { size: q.Var('size'), before: q.Var('before') }),
            )
          )
        ),
      })
    )
    .then((ret) => console.log(ret))
    .catch((err) => console.error(
      'Error: [%s] %s: %s',
      err.name,
      err.message,
      err.errors()[0].description,
    ))
    {
      ref: Function("paginate_helper"),
      ts: 1654791433630000,
      name: 'paginate_helper',
      body: Query(Lambda(["match", "size", "after", "before"], If(Equals(Var("before"), null), If(Equals(Var("after"), null), Paginate(Var("match"), {"size": Var("size")}), Paginate(Var("match"), {"after": Var("after"), "size": Var("size")})), Paginate(Var("match"), {"before": Var("before"), "size": Var("size")}))))
    }
    result = client.query(
      q.create_function({
        "name": "paginate_helper",
        "body": q.query(
          q.lambda_(
            ["match", "size", "after", "before"],
            q.if_(
              q.equals(q.var("before"), None),
              q.if_(
                q.equals(q.var("after"), None),
                q.paginate(q.var("match"), size=q.var("size")),
                q.paginate(q.var("match"), size=q.var("size"), after=q.var("after"))
              ),
              q.paginate(q.var("match"), size=q.var("size"), before=q.var("before")),
            )
          )
        )
      })
    )
    print(result)
    {'ref': Ref(id=paginate_helper, collection=Ref(id=functions)), 'ts': 1655424431870000, 'name': 'paginate_helper', 'body': Query({'api_version': '4', 'lambda': ['match', 'size', 'after', 'before'], 'expr': {'if': {'equals': [{'var': 'before'}, None]}, 'then': {'if': {'equals': [{'var': 'after'}, None]}, 'then': {'paginate': {'var': 'match'}, 'size': {'var': 'size'}}, 'else': {'paginate': {'var': 'match'}, 'after': {'var': 'after'}, 'size': {'var': 'size'}}}, 'else': {'paginate': {'var': 'match'}, 'before': {'var': 'before'}, 'size': {'var': 'size'}}}})}
    CreateFunction({
      name: "paginate_helper",
      body: Query(
        Lambda(
          ["match", "size", "after", "before"],
          If(
            Equals(Var("before"), null),
            If(
              Equals(Var("after"), null),
              Paginate(Var("match"), { size: Var("size") }),
              Paginate(Var("match"), { size: Var("size"), after: Var("after") })
            ),
            Paginate(Var("match"), { size: Var("size"), before: Var("before") }),
          )
        )
      )
    })
    {
      ref: Function("paginate_helper"),
      ts: 1654791439670000,
      name: 'paginate_helper',
      body: Query(Lambda(["match", "size", "after", "before"], If(Equals(Var("before"), null), If(Equals(Var("after"), null), Paginate(Var("match"), {"size": Var("size")}), Paginate(Var("match"), {"after": Var("after"), "size": Var("size")})), Paginate(Var("match"), {"before": Var("before"), "size": Var("size")}))))
    }
    Query metrics:
    •    bytesIn: 440

    •   bytesOut: 544

    • computeOps:   1

    •    readOps:   0

    •   writeOps:   1

    •  readBytes:  28

    • writeBytes: 607

    •  queryTime: 9ms

    •    retries:   0

  4. Update the auto-generated itemsWithComponent resolver UDF.

    Auto-generated resolver UDFs are only stub functions, so we have to provide the implementation that we need.

    The following code sample includes variations for the supported Drivers, as well as a Shell example that you can copy+paste into the Shell tab in the Fauna Dashboard.

    try
    {
        Value result = await client.Query(
            Update(
                Function("itemsWithComponent"),
                Obj(
                    "body", Query(
                        Lambda(
                            Arr("component", "size", "after", "before"),
                            Let(
                                "match", Match(
                                    Index("MediaItem_has_component"),
                                    Var("component")
                                ),
                                "page", Call(
                                    "paginate_helper",
                                    Arr(
                                        Var("match"),
                                        Var("size"),
                                        Var("after"),
                                        Var("before")
                                    )
                                )
                            ).In(
                                Map(
                                    Var("page"),
                                    Lambda(
                                        "ref",
                                        Get(Var("ref"))
                                    )
                                )
                            )
                        )
                    )
                )
            )
        );
        Console.WriteLine(result);
    }
    catch (Exception e)
    {
        Console.WriteLine($"ERROR: {e.Message}");
    }
    ObjectV(ref: RefV(id = "itemsWithComponent", collection = RefV(id = "functions")),ts: LongV(1654896505910000),name: StringV(itemsWithComponent),data: ObjectV(gql: ObjectV(ts: FaunaTime(2022-06-10T21:28:16.399Z),meta: ObjectV(location: StringV(Query),field: ObjectV(name: StringV(itemsWithComponent),directives: Arr(ObjectV(name: StringV(resolver),args: ObjectV(name: StringV(itemsWithComponent),paginated: BooleanV(True)))),type: ObjectV(NotNull: ObjectV(List: ObjectV(Named: StringV(MediaItem)))),arguments: Arr(ObjectV(name: StringV(component),type: ObjectV(NotNull: ObjectV(Named: StringV(String))))))))),body: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))
    The Go version of this example is not currently available.
    System.out.println(
        client.query(
            Update(
                Function("itemsWithComponent"),
                Obj(
                    "body", Query(
                        Lambda(
                            Arr(
                                Value("component"),
                                Value("size"),
                                Value("after"),
                                Value("before")
                            ),
                            Let(
                                "match", Match(
                                    Index("MediaItem_has_component"),
                                    Var("component")
                                ),
                                "page", Call(
                                    Function("paginate_helper"),
                                    Arr(
                                        Var("match"),
                                        Var("size"),
                                        Var("after"),
                                        Var("before")
                                    )
                                )
                            ).in(
                                Map(
                                    Var("page"),
                                    Lambda(
                                        "ref",
                                        Get(Var("ref"))
                                    )
                                )
                            )
                        )
                    )
                )
            )
        ).get());
    {ref: ref(id = "itemsWithComponent", collection = ref(id = "functions")), ts: 1654897645750000, name: "itemsWithComponent", data: {gql: {ts: 2022-06-10T21:46:50.548Z, meta: {location: "Query", field: {name: "itemsWithComponent", directives: [{name: "resolver", args: {name: "itemsWithComponent", paginated: true}}], type: {NotNull: {List: {Named: "MediaItem"}}}, arguments: [{name: "component", type: {NotNull: {Named: "String"}}}]}}}}, body: QueryV({api_version=4, lambda=[component, size, after, before], expr={let={match={match={index=MediaItem_has_component}, terms={var=component}}, page={call={function=paginate_helper}, arguments=[{var=match}, {var=size}, {var=after}, {var=before}]}}, in={map={lambda=ref, expr={get={var=ref}}}, collection={var=page}}}})}
    client.query(
      q.Update(
        q.Function('itemsWithComponent'),
        {
          body: q.Query(
            q.Lambda(
              ['component', 'size', 'after', 'before'],
              q.Let(
                {
                  match: q.Match(
                    q.Index('MediaItem_has_component'),
                    q.Var('component')
                  ),
                  page: q.Call(
                    'paginate_helper',
                    [
                      q.Var('match'),
                      q.Var('size'),
                      q.Var('after'),
                      q.Var('before'),
                    ],
                  ),
                },
                q.Map(
                  q.Var('page'),
                  q.Lambda('ref', q.Get(q.Var('ref')))
                )
              )
            )
          ),
        },
      )
    )
    .then((ret) => console.log(ret))
    .catch((err) => console.error(
      'Error: [%s] %s: %s',
      err.name,
      err.message,
      err.errors()[0].description,
    ))
    {
      ref: Function("itemsWithComponent"),
      ts: 1654893973790000,
      name: 'itemsWithComponent',
      data: {
        gql: {
          ts: Time("2022-06-10T20:46:13.490Z"),
          meta: {
            location: 'Query',
            field: {
              name: 'itemsWithComponent',
              directives: [
                {
                  name: 'resolver',
                  args: { name: 'itemsWithComponent', paginated: true }
                }
              ],
              type: { NotNull: { List: { Named: 'MediaItem' } } },
              arguments: [
                {
                  name: 'component',
                  type: { NotNull: { Named: 'String' } }
                }
              ]
            }
          }
        }
      },
      body: Query(Lambda(["component", "size", "after", "before"], Let({"match": Match(Index("MediaItem_has_component"), Var("component")), "page": Call("paginate_helper", [Var("match"), Var("size"), Var("after"), Var("before")])}, Map(Var("page"), Lambda("ref", Get(Var("ref")))))))
    }
    result = client.query(
      q.update(
        q.function("itemsWithComponent"),
        {
          "body": q.query(
            q.lambda_(
              ["component", "size", "after", "before"],
              q.let(
                {
                  "match": q.match(
                    q.index("MediaItem_has_component"),
                    q.var("component")
                  ),
                  "page": q.call(
                    "paginate_helper",
                    [
                      q.var("match"),
                      q.var("size"),
                      q.var("after"),
                      q.var("before")
                    ]
                  )
                },
                q.map_(
                  q.lambda_(
                    "ref",
                    q.get(q.var("ref"))
                  ),
                  q.var("page")
                )
              )
            )
          )
        }
      )
    )
    print(result)
    {'ref': Ref(id=itemsWithComponent, collection=Ref(id=functions)), 'ts': 1654898104170000, 'name': 'itemsWithComponent', 'data': {'gql': {'ts': FaunaTime('2022-06-10T21:55:01.598Z'), 'meta': {'location': 'Query', 'field': {'name': 'itemsWithComponent', 'directives': [{'name': 'resolver', 'args': {'name': 'itemsWithComponent', 'paginated': True}}], 'type': {'NotNull': {'List': {'Named': 'MediaItem'}}}, 'arguments': [{'name': 'component', 'type': {'NotNull': {'Named': 'String'}}}]}}}}, 'body': Query({'api_version': '4', 'lambda': ['component', 'size', 'after', 'before'], 'expr': {'let': {'match': {'match': {'index': 'MediaItem_has_component'}, 'terms': {'var': 'component'}}, 'page': {'call': 'paginate_helper', 'arguments': [{'var': 'match'}, {'var': 'size'}, {'var': 'after'}, {'var': 'before'}]}}, 'in': {'map': {'lambda': 'ref', 'expr': {'get': {'var': 'ref'}}}, 'collection': {'var': 'page'}}}})}
    Update(
      Function("itemsWithComponent"),
      {
        body: Query(
          Lambda(
            ["component", "size", "after", "before"],
            Let(
              {
                match: Match(
                  Index("MediaItem_has_component"),
                  Var("component")
                ),
                page: Call(
                  "paginate_helper",
                  [
                    Var("match"),
                    Var("size"),
                    Var("after"),
                    Var("before")
                  ]
                )
              },
              Map(Var("page"), Lambda("ref", Get(Var("ref"))))
            )
          )
        )
      }
    )
    {
      ref: Function("itemsWithComponent"),
      ts: 1654893376660000,
      name: 'itemsWithComponent',
      data: {
        gql: {
          ts: Time("2022-06-10T20:36:15.483Z"),
          meta: {
            location: 'Query',
            field: {
              name: 'itemsWithComponent',
              directives: [
                {
                  name: 'resolver',
                  args: { name: 'itemsWithComponent', paginated: true }
                }
              ],
              type: { NotNull: { List: { Named: 'MediaItem' } } },
              arguments: [
                {
                  name: 'component',
                  type: { NotNull: { Named: 'String' } }
                }
              ]
            }
          }
        }
      },
      body: Query(Lambda(["component", "size", "after", "before"], Let({"match": Match(Index("MediaItem_has_component"), Var("component")), "page": Call("paginate_helper", [Var("match"), Var("size"), Var("after"), Var("before")])}, Map(Var("page"), Lambda("ref", Get(Var("ref")))))))
    }
    Query metrics:
    •    bytesIn:  434

    •   bytesOut:  866

    • computeOps:    1

    •    readOps:    0

    •   writeOps:    1

    •  readBytes:  491

    • writeBytes:  766

    •  queryTime: 15ms

    •    retries:    0

  5. Update the auto-generated itemsWithAllComponents resolver UDF.

    The following code sample includes variations for the supported Drivers, as well as a Shell example that you can copy+paste into the Shell tab in the Fauna Dashboard.

    try
    {
        Value result = await client.Query(
            Update(
                Function("itemsWithAllComponents"),
                Obj(
                    "body", Query(
                        Lambda(
                            Arr("components", "size", "after", "before"),
                            Let(
                                "match", Intersection(
                                    Map(
                                        Var("components"),
                                        Lambda(
                                            "component",
                                            Match(
                                                Index("MediaItem_has_component"),
                                                Var("component")
                                            )
                                        )
                                    )
                                ),
                                "page", Call(
                                    "paginate_helper",
                                    Arr(
                                        Var("match"),
                                        Var("size"),
                                        Var("after"),
                                        Var("before")
                                    )
                                )
                            ).In(
                                Map(
                                    Var("page"),
                                    Lambda(
                                        "ref",
                                        Get(Var("ref"))
                                    )
                                )
                            )
                        )
                    )
                )
            )
        );
        Console.WriteLine(result);
    }
    catch (Exception e)
    {
        Console.WriteLine($"ERROR: {e.Message}");
    }
    ObjectV(ref: RefV(id = "itemsWithAllComponents", collection = RefV(id = "functions")),ts: LongV(1654900917990000),name: StringV(itemsWithAllComponents),data: ObjectV(gql: ObjectV(ts: FaunaTime(2022-06-10T22:41:44.598Z),meta: ObjectV(location: StringV(Query),field: ObjectV(name: StringV(itemsWithAllComponents),directives: Arr(ObjectV(name: StringV(resolver),args: ObjectV(name: StringV(itemsWithAllComponents),paginated: BooleanV(True)))),type: ObjectV(NotNull: ObjectV(List: ObjectV(Named: StringV(MediaItem)))),arguments: Arr(ObjectV(name: StringV(components),type: ObjectV(NotNull: ObjectV(List: ObjectV(NotNull: ObjectV(Named: StringV(String))))))))))),body: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))
    The Go version of this example is not currently available.
    System.out.println(
        client.query(
            Update(
                Function("itemsWithAllComponents"),
                Obj(
                    "body", Query(
                        Lambda(
                            Arr(
                                Value("components"),
                                Value("size"),
                                Value("after"),
                                Value("before")
                            ),
                            Let(
                                "match", Intersection(
                                    Map(
                                        Var("components"),
                                        Lambda(
                                            "component",
                                            Match(
                                                Index("MediaItem_has_component"),
                                                Var("component")
                                            )
                                        )
                                    )
                                ),
                                "page", Call(
                                    Function("paginate_helper"),
                                    Arr(
                                        Var("match"),
                                        Var("size"),
                                        Var("after"),
                                        Var("before")
                                    )
                                )
                            ).in(
                                Map(
                                    Var("page"),
                                    Lambda(
                                        "ref",
                                        Get(Var("ref"))
                                    )
                                )
                            )
                        )
                    )
                )
            )
        ).get());
    {ref: ref(id = "itemsWithAllComponents", collection = ref(id = "functions")), ts: 1654899752410000, name: "itemsWithAllComponents", data: {gql: {ts: 2022-06-10T22:21:45.316Z, meta: {location: "Query", field: {name: "itemsWithAllComponents", directives: [{name: "resolver", args: {name: "itemsWithAllComponents", paginated: true}}], type: {NotNull: {List: {Named: "MediaItem"}}}, arguments: [{name: "components", type: {NotNull: {List: {NotNull: {Named: "String"}}}}}]}}}}, body: QueryV({api_version=4, lambda=[components, size, after, before], expr={let={match={intersection={map={lambda=component, expr={match={index=MediaItem_has_component}, terms={var=component}}}, collection={var=components}}}, page={call={function=paginate_helper}, arguments=[{var=match}, {var=size}, {var=after}, {var=before}]}}, in={map={lambda=ref, expr={get={var=ref}}}, collection={var=page}}}})}
    client.query(
      q.Update(
        q.Function('itemsWithAllComponents'),
        {
          body: q.Query(
            q.Lambda(
              ['components', 'size', 'after', 'before'],
              q.Let(
                {
                  match: q.Intersection(
                    q.Map(
                      q.Var('components'),
                      q.Lambda(
                        'component',
                        q.Match(
                          q.Index('MediaItem_has_component'),
                          q.Var('component'),
                        ),
                      ),
                    )
                  ),
                  page: q.Call(
                    'paginate_helper',
                    [
                      q.Var('match'),
                      q.Var('size'),
                      q.Var('after'),
                      q.Var('before'),
                    ],
                  ),
                },
                q.Map(
                  q.Var('page'),
                  q.Lambda('ref', q.Get(q.Var('ref')))
                )
              )
            )
          ),
        },
      )
    )
    .then((ret) => console.log(ret))
    .catch((err) => console.error(
      'Error: [%s] %s: %s',
      err.name,
      err.message,
      err.errors()[0].description,
    ))
    {
      ref: Function("itemsWithAllComponents"),
      ts: 1654899476190000,
      name: 'itemsWithAllComponents',
      data: {
        gql: {
          ts: Time("2022-06-10T22:17:55.662Z"),
          meta: {
            location: 'Query',
            field: {
              name: 'itemsWithAllComponents',
              directives: [
                {
                  name: 'resolver',
                  args: { name: 'itemsWithAllComponents', paginated: true }
                }
              ],
              type: { NotNull: { List: { Named: 'MediaItem' } } },
              arguments: [
                {
                  name: 'components',
                  type: {
                    NotNull: { List: { NotNull: { Named: 'String' } } }
                  }
                }
              ]
            }
          }
        }
      },
      body: Query(Lambda(["components", "size", "after", "before"], Let({"match": Intersection(Map(Var("components"), Lambda("component", Match(Index("MediaItem_has_component"), Var("component"))))), "page": Call("paginate_helper", [Var("match"), Var("size"), Var("after"), Var("before")])}, Map(Var("page"), Lambda("ref", Get(Var("ref")))))))
    }
    result = client.query(
      q.update(
        q.function("itemsWithAllComponents"),
        {
          "body": q.query(
            q.lambda_(
              ["components", "size", "after", "before"],
              q.let(
                {
                  "match": q.intersection(
                    q.map_(
                      q.lambda_(
                        "component",
                        q.match(
                          q.index("MediaItem_has_component"),
                          q.var("component")
                        )
                      ),
                      q.var("components")
                    )
                  ),
                  "page": q.call(
                    "paginate_helper",
                    [
                      q.var("match"),
                      q.var("size"),
                      q.var("after"),
                      q.var("before")
                    ]
                  )
                },
                q.map_(
                  q.lambda_(
                    "ref",
                    q.get(q.var("ref"))
                  ),
                  q.var("page")
                )
              )
            )
          )
        }
      )
    )
    print(result)
    {'ref': Ref(id=itemsWithAllComponents, collection=Ref(id=functions)), 'ts': 1654899353360000, 'name': 'itemsWithAllComponents', 'data': {'gql': {'ts': FaunaTime('2022-06-10T22:15:49.739Z'), 'meta': {'location': 'Query', 'field': {'name': 'itemsWithAllComponents', 'directives': [{'name': 'resolver', 'args': {'name': 'itemsWithAllComponents', 'paginated': True}}], 'type': {'NotNull': {'List': {'Named': 'MediaItem'}}}, 'arguments': [{'name': 'components', 'type': {'NotNull': {'List': {'NotNull': {'Named': 'String'}}}}}]}}}}, 'body': Query({'api_version': '4', 'lambda': ['components', 'size', 'after', 'before'], 'expr': {'let': {'match': {'intersection': {'map': {'lambda': 'component', 'expr': {'match': {'index': 'MediaItem_has_component'}, 'terms': {'var': 'component'}}}, 'collection': {'var': 'components'}}}, 'page': {'call': 'paginate_helper', 'arguments': [{'var': 'match'}, {'var': 'size'}, {'var': 'after'}, {'var': 'before'}]}}, 'in': {'map': {'lambda': 'ref', 'expr': {'get': {'var': 'ref'}}}, 'collection': {'var': 'page'}}}})}
    Update(
      Function("itemsWithAllComponents"),
      {
        body: Query(
          Lambda(
            ["components", "size", "after", "before"],
            Let(
              {
                match: Intersection(
                  Map(
                    Var("components"),
                    Lambda(
                      "component",
                      Match(
                        Index("MediaItem_has_component"),
                        Var("component")
                      )
                    )
                  )
                ),
                page: Call(
                  "paginate_helper",
                  [
                    Var("match"),
                    Var("size"),
                    Var("after"),
                    Var("before")
                  ]
                )
              },
              Map(Var("page"), Lambda("ref", Get(Var("ref"))))
            )
          )
        )
      }
    )
    {
      ref: Function("itemsWithAllComponents"),
      ts: 1654899106920000,
      name: 'itemsWithAllComponents',
      data: {
        gql: {
          ts: Time("2022-06-10T22:11:45.474Z"),
          meta: {
            location: 'Query',
            field: {
              name: 'itemsWithAllComponents',
              directives: [
                {
                  name: 'resolver',
                  args: { name: 'itemsWithAllComponents', paginated: true }
                }
              ],
              type: { NotNull: { List: { Named: 'MediaItem' } } },
              arguments: [
                {
                  name: 'components',
                  type: {
                    NotNull: { List: { NotNull: { Named: 'String' } } }
                  }
                }
              ]
            }
          }
        }
      },
      body: Query(Lambda(["components", "size", "after", "before"], Let({"match": Intersection(Map(Var("components"), Lambda("component", Match(Index("MediaItem_has_component"), Var("component"))))), "page": Call("paginate_helper", [Var("match"), Var("size"), Var("after"), Var("before")])}, Map(Var("page"), Lambda("ref", Get(Var("ref")))))))
    }
    Query metrics:
    •    bytesIn:  528

    •   bytesOut:  994

    • computeOps:    1

    •    readOps:    0

    •   writeOps:    1

    •  readBytes:  527

    • writeBytes:  872

    •  queryTime: 10ms

    •    retries:    0

    The Intersection function is used to match all components.
  6. Update the itemsWithAnyComponents resolver UDF.

    The following code sample includes variations for the supported Drivers, as well as a Shell example that you can copy+paste into the Shell tab in the Fauna Dashboard.

    try
    {
        Value result = await client.Query(
            Update(
                Function("itemsWithAnyComponents"),
                Obj(
                    "body", Query(
                        Lambda(
                            Arr("components", "size", "after", "before"),
                            Let(
                                "match", Union(
                                    Map(
                                        Var("components"),
                                        Lambda(
                                            "component",
                                            Match(
                                                Index("MediaItem_has_component"),
                                                Var("component")
                                            )
                                        )
                                    )
                                ),
                                "page", Call(
                                    "paginate_helper",
                                    Arr(
                                        Var("match"),
                                        Var("size"),
                                        Var("after"),
                                        Var("before")
                                    )
                                )
                            ).In(
                                Map(
                                    Var("page"),
                                    Lambda(
                                        "ref",
                                        Get(Var("ref"))
                                    )
                                )
                            )
                        )
                    )
                )
            )
        );
        Console.WriteLine(result);
    }
    catch (Exception e)
    {
        Console.WriteLine($"ERROR: {e.Message}");
    }
    ObjectV(ref: RefV(id = "itemsWithAnyComponents", collection = RefV(id = "functions")),ts: LongV(1655142746710000),name: StringV(itemsWithAnyComponents),data: ObjectV(gql: ObjectV(ts: FaunaTime(2022-06-13T17:52:08.929Z),meta: ObjectV(location: StringV(Query),field: ObjectV(name: StringV(itemsWithAnyComponents),directives: Arr(ObjectV(name: StringV(resolver),args: ObjectV(name: StringV(itemsWithAnyComponents),paginated: BooleanV(True)))),type: ObjectV(NotNull: ObjectV(List: ObjectV(Named: StringV(MediaItem)))),arguments: Arr(ObjectV(name: StringV(components),type: ObjectV(NotNull: ObjectV(List: ObjectV(NotNull: ObjectV(Named: StringV(String))))))))))),body: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))
    The Go version of this example is not currently available.
    System.out.println(
        client.query(
            Update(
                Function("itemsWithAnyComponents"),
                Obj(
                    "body", Query(
                        Lambda(
                            Arr(
                                Value("components"),
                                Value("size"),
                                Value("after"),
                                Value("before")
                            ),
                            Let(
                                "match", Union(
                                    Map(
                                        Var("components"),
                                        Lambda(
                                            "component",
                                            Match(
                                                Index("MediaItem_has_component"),
                                                Var("component")
                                            )
                                        )
                                    )
                                ),
                                "page", Call(
                                    Function("paginate_helper"),
                                    Arr(
                                        Var("match"),
                                        Var("size"),
                                        Var("after"),
                                        Var("before")
                                    )
                                )
                            ).in(
                                Map(
                                    Var("page"),
                                    Lambda(
                                        "ref",
                                        Get(Var("ref"))
                                    )
                                )
                            )
                        )
                    )
                )
            )
        ).get());
    {ref: ref(id = "itemsWithAnyComponents", collection = ref(id = "functions")), ts: 1655142852770000, name: "itemsWithAnyComponents", data: {gql: {ts: 2022-06-13T17:53:12.289Z, meta: {location: "Query", field: {name: "itemsWithAnyComponents", directives: [{name: "resolver", args: {name: "itemsWithAnyComponents", paginated: true}}], type: {NotNull: {List: {Named: "MediaItem"}}}, arguments: [{name: "components", type: {NotNull: {List: {NotNull: {Named: "String"}}}}}]}}}}, body: QueryV({api_version=4, lambda=[components, size, after, before], expr={let={match={union={map={lambda=component, expr={match={index=MediaItem_has_component}, terms={var=component}}}, collection={var=components}}}, page={call={function=paginate_helper}, arguments=[{var=match}, {var=size}, {var=after}, {var=before}]}}, in={map={lambda=ref, expr={get={var=ref}}}, collection={var=page}}}})}
    client.query(
      q.Update(
        q.Function('itemsWithAnyComponents'),
        {
          body: q.Query(
            q.Lambda(
              ['components', 'size', 'after', 'before'],
              q.Let(
                {
                  match: q.Union(
                    q.Map(
                      q.Var('components'),
                      q.Lambda(
                        'component',
                        q.Match(
                          q.Index('MediaItem_has_component'),
                          q.Var('component'),
                        ),
                      ),
                    )
                  ),
                  page: q.Call(
                    'paginate_helper',
                    [
                      q.Var('match'),
                      q.Var('size'),
                      q.Var('after'),
                      q.Var('before'),
                    ],
                  ),
                },
                q.Map(
                  q.Var('page'),
                  q.Lambda('ref', q.Get(q.Var('ref')))
                )
              )
            )
          ),
        },
      )
    )
    .then((ret) => console.log(ret))
    .catch((err) => console.error(
      'Error: [%s] %s: %s',
      err.name,
      err.message,
      err.errors()[0].description,
    ))
    {
      ref: Function("itemsWithAnyComponents"),
      ts: 1655142530560000,
      name: 'itemsWithAnyComponents',
      data: {
        gql: {
          ts: Time("2022-06-13T17:48:49.894Z"),
          meta: {
            location: 'Query',
            field: {
              name: 'itemsWithAnyComponents',
              directives: [
                {
                  name: 'resolver',
                  args: { name: 'itemsWithAnyComponents', paginated: true }
                }
              ],
              type: { NotNull: { List: { Named: 'MediaItem' } } },
              arguments: [
                {
                  name: 'components',
                  type: {
                    NotNull: { List: { NotNull: { Named: 'String' } } }
                  }
                }
              ]
            }
          }
        }
      },
      body: Query(Lambda(["components", "size", "after", "before"], Let({"match": Union(Map(Var("components"), Lambda("component", Match(Index("MediaItem_has_component"), Var("component"))))), "page": Call("paginate_helper", [Var("match"), Var("size"), Var("after"), Var("before")])}, Map(Var("page"), Lambda("ref", Get(Var("ref")))))))
    }
    result = client.query(
      q.update(
        q.function("itemsWithAnyComponents"),
        {
          "body": q.query(
            q.lambda_(
              ["components", "size", "after", "before"],
              q.let(
                {
                  "match": q.union(
                    q.map_(
                      q.lambda_(
                        "component",
                        q.match(
                          q.index("MediaItem_has_component"),
                          q.var("component")
                        )
                      ),
                      q.var("components")
                    )
                  ),
                  "page": q.call(
                    "paginate_helper",
                    [
                      q.var("match"),
                      q.var("size"),
                      q.var("after"),
                      q.var("before")
                    ]
                  )
                },
                q.map_(
                  q.lambda_(
                    "ref",
                    q.get(q.var("ref"))
                  ),
                  q.var("page")
                )
              )
            )
          )
        }
      )
    )
    print(result)
    {'ref': Ref(id=itemsWithAnyComponents, collection=Ref(id=functions)), 'ts': 1655142956620000, 'name': 'itemsWithAnyComponents', 'data': {'gql': {'ts': FaunaTime('2022-06-13T17:55:51.408Z'), 'meta': {'location': 'Query', 'field': {'name': 'itemsWithAnyComponents', 'directives': [{'name': 'resolver', 'args': {'name': 'itemsWithAnyComponents', 'paginated': True}}], 'type': {'NotNull': {'List': {'Named': 'MediaItem'}}}, 'arguments': [{'name': 'components', 'type': {'NotNull': {'List': {'NotNull': {'Named': 'String'}}}}}]}}}}, 'body': Query({'api_version': '4', 'lambda': ['components', 'size', 'after', 'before'], 'expr': {'let': {'match': {'union': {'map': {'lambda': 'component', 'expr': {'match': {'index': 'MediaItem_has_component'}, 'terms': {'var': 'component'}}}, 'collection': {'var': 'components'}}}, 'page': {'call': 'paginate_helper', 'arguments': [{'var': 'match'}, {'var': 'size'}, {'var': 'after'}, {'var': 'before'}]}}, 'in': {'map': {'lambda': 'ref', 'expr': {'get': {'var': 'ref'}}}, 'collection': {'var': 'page'}}}})}
    Update(
      Function("itemsWithAnyComponents"),
      {
        body: Query(
          Lambda(
            ["components", "size", "after", "before"],
            Let(
              {
                match: Union(
                  Map(
                    Var("components"),
                    Lambda(
                      "component",
                      Match(
                        Index("MediaItem_has_component"),
                        Var("component")
                      )
                    )
                  )
                ),
                page: Call(
                  "paginate_helper",
                  [
                    Var("match"),
                    Var("size"),
                    Var("after"),
                    Var("before")
                  ]
                )
              },
              Map(Var("page"), Lambda("ref", Get(Var("ref"))))
            )
          )
        )
      }
    )
    {
      ref: Function("itemsWithAnyComponents"),
      ts: 1654901367040000,
      name: 'itemsWithAnyComponents',
      data: {
        gql: {
          ts: Time("2022-06-10T22:49:25.339Z"),
          meta: {
            location: 'Query',
            field: {
              name: 'itemsWithAnyComponents',
              directives: [
                {
                  name: 'resolver',
                  args: { name: 'itemsWithAnyComponents', paginated: true }
                }
              ],
              type: { NotNull: { List: { Named: 'MediaItem' } } },
              arguments: [
                {
                  name: 'components',
                  type: {
                    NotNull: { List: { NotNull: { Named: 'String' } } }
                  }
                }
              ]
            }
          }
        }
      },
      body: Query(Lambda(["components", "size", "after", "before"], Let({"match": Union(Map(Var("components"), Lambda("component", Match(Index("MediaItem_has_component"), Var("component"))))), "page": Call("paginate_helper", [Var("match"), Var("size"), Var("after"), Var("before")])}, Map(Var("page"), Lambda("ref", Get(Var("ref")))))))
    }
    Query metrics:
    •    bytesIn:  521

    •   bytesOut:  987

    • computeOps:    1

    •    readOps:    0

    •   writeOps:    1

    •  readBytes:  527

    • writeBytes:  865

    •  queryTime: 13ms

    •    retries:    0

    The Union function is used to match any components.
  7. Run a GraphQL mutation to create a User and some MediaItems. Use the GraphQL tab in the Fauna Dashboard to execute the following mutation:

    mutation {
      createUser(
        data: {
          name: "Alice",
          favorites: {
            create: [
              {
                title: "Black Widow",
                components: {
                  movie: { length: 2.2 }
                  newRelease: true
                }
              },
              {
                title: "Psycho"
                components: {
                  movie: { length:1.8 }
                  promoted: true
                }
              },
              {
                title: "The Expanse"
                components: {
                  show: { seasons: 6 }
                  newRelease: true
                }
              },
              {
                title: "Fellowship of the Ring"
                components: {
                  book: { pages: 544 }
                  promoted: true
                }
              }
            ]
          }
        }
      ) {
        name
        _id
        favorites {
          data {
            title
            components {
              movie { length }
              show { seasons }
              book { pages }
              newRelease
              promoted
            }
            _id
          }
        }
      }
    }

    The result should be similar to:

    {
      "data": {
        "createUser": {
          "name": "Alice",
          "_id": "332207514480280098",
          "favorites": {
            "data": [
              {
                "title": "Black Widow",
                "components": {
                  "movie": {
                    "length": 2.2
                  },
                  "show": null,
                  "book": null,
                  "newRelease": true,
                  "promoted": null
                },
                "_id": "332207514484474402"
              },
              {
                "title": "Psycho",
                "components": {
                  "movie": {
                    "length": 1.8
                  },
                  "show": null,
                  "book": null,
                  "newRelease": null,
                  "promoted": true
                },
                "_id": "332207514493911586"
              },
              {
                "title": "The Expanse",
                "components": {
                  "movie": null,
                  "show": {
                    "seasons": 6
                  },
                  "book": null,
                  "newRelease": true,
                  "promoted": null
                },
                "_id": "332207514497057314"
              },
              {
                "title": "Fellowship of the Ring",
                "components": {
                  "movie": null,
                  "show": null,
                  "book": {
                    "pages": 544
                  },
                  "newRelease": null,
                  "promoted": true
                },
                "_id": "332207514500203042"
              }
            ]
          }
        }
      }
    }
    The document IDs presented in the _id fields are different for every new document created in Fauna. The Objective 3 query requires two of the document IDs present in this result.

Objective 1: Query for all entities of a given type

You can get all items of a type by indexing on the existence of the component field.

{
  allMovies: itemsWithComponent(component: "movie") {
    data {
      title
      components {
        movie { length }
        newRelease
        promoted
      }
    }
  }

  allShows: itemsWithComponent(component: "show") {
    data {
      title
      components {
        show { seasons }
        newRelease
        promoted
      }
    }
  }

  allBooks: itemsWithComponent(component: "book") {
    data {
      title
      components {
        book { pages }
        newRelease
        promoted
      }
    }
  }
}
{
  "data": {
    "allMovies": {
      "data": [
        {
          "title": "Black Widow",
          "components": {
            "movie": {
              "length": 2.2
            },
            "newRelease": true,
            "promoted": null
          }
        },
        {
          "title": "Psycho",
          "components": {
            "movie": {
              "length": 1.8
            },
            "newRelease": null,
            "promoted": true
          }
        }
      ]
    },
    "allShows": {
      "data": [
        {
          "title": "The Expanse",
          "components": {
            "show": {
              "seasons": 6
            },
            "newRelease": true,
            "promoted": null
          }
        }
      ]
    },
    "allBooks": {
      "data": [
        {
          "title": "Fellowship of the Ring",
          "components": {
            "book": {
              "pages": 544
            },
            "newRelease": null,
            "promoted": true
          }
        }
      ]
    }
  }
}

Objective 2: Query for all entities with certain properties

Every entity is searchable by every property. You can use the current schema to match all items that have multiple components.

Additional Indexes can be created to extend the above schema to match on terms such as data.components.book.author.

{
  allMovies: itemsWithAllComponents(components: ["movie", "newRelease"]) {
    data {
      title
      components {
        movie { length }
        newRelease
        promoted
      }
    }
  }
}
{
  "data": {
    "allMovies": {
      "data": [
        {
          "title": "Black Widow",
          "components": {
            "movie": {
              "length": 2.2
            },
            "newRelease": true,
            "promoted": null
          }
        }
      ]
    }
  }
}

Objective 3: Query for all relationships of an entity, regardless of which type it points to

  1. You can search for all user favorites by selecting the single favorites field.

  2. You can search for all users that have the media item marked as a favorite through the favoritedBy field.

To make the following query work:

  • replace <$USERID> with the document ID for the user created in the setup’s step 7 (just below the name field in the result).

  • replace <$MEDIAITEMID> with the document ID for the "Black Widow" movie created in the setup’s step 7.

{
  findUserByID(id: "<$USERID>") {
    name
    favorites {
      data {
        title
        components {
          movie { length }
          show { seasons }
          book { pages }
          newRelease
          promoted
        }
      }
    }
  }

  findMediaItemByID(id:"<$MEDIAITEMID>") {
    title
    components {
      movie { length }
      show { seasons }
      book { pages }
      newRelease
      promoted
    }
    favoritedBy {
      data { name }
    }
  }
}
{
  "data": {
    "findUserByID": {
      "name": "Alice",
      "favorites": {
        "data": [
          {
            "title": "Black Widow",
            "components": {
              "movie": {
                "length": 2.2
              },
              "show": null,
              "book": null,
              "newRelease": true,
              "promoted": null
            }
          },
          {
            "title": "Psycho",
            "components": {
              "movie": {
                "length": 1.8
              },
              "show": null,
              "book": null,
              "newRelease": null,
              "promoted": true
            }
          },
          {
            "title": "The Expanse",
            "components": {
              "movie": null,
              "show": {
                "seasons": 6
              },
              "book": null,
              "newRelease": true,
              "promoted": null
            }
          },
          {
            "title": "Fellowship of the Ring",
            "components": {
              "movie": null,
              "show": null,
              "book": {
                "pages": 544
              },
              "newRelease": null,
              "promoted": true
            }
          }
        ]
      }
    },
    "findMediaItemByID": {
      "title": "Black Widow",
      "components": {
        "movie": {
          "length": 2.2
        },
        "show": null,
        "book": null,
        "newRelease": true,
        "promoted": null
      },
      "favoritedBy": {
        "data": [
          {
            "name": "Alice"
          }
        ]
      }
    }
  }
}

Is this article helpful? 

Tell Fauna how the article can be improved:
Visit Fauna's forums or email docs@fauna.com

Thank you for your feedback!