Skip to content

Queries

Queries are the way to read data from a node. Now that you know how schemas shape a program’s graph, let’s talk about how queries work.

To illustrate this section, we’ll assume that you have membrane/github running on your workspace. We’ll also assume that you have created a blank program with the following node as a connection, and named the connection user:

NameGrefType
usergithub:users.one(name:'membrane-io')

You can click on the type above to explore the entire schema of

membrane/github.

We’ll be querying data from the above Github User. Feel free to change it to your own GitHub username.

Querying a node

Now that your program has access to a node named user, you can use $query and $get to read values from it.

Here are some examples:

import {
const nodes: {
readonly user: github.User;
readonly clock: Clock;
readonly process: Process;
}

Contains the graph references (grefs) that this program has been given access to.

nodes
} from "membrane"
async function
function run(): Promise<void>
run
() {
// Using $get() to query a string
const
const bio: string
bio
= await
const nodes: {
readonly user: github.User;
readonly clock: Clock;
readonly process: Process;
}

Contains the graph references (grefs) that this program has been given access to.

nodes
.
user: github.handles.User
user
.
github.handles.User.bio: Scalar<string> & (() => Scalar<string>)

The user's bio

bio
.
Scalar<string>.$get: () => Promise<string>

Gets the value of this field. This is equivalent to calling $query without arguments

$get
()
// For convenience, you can omit the $get() when querying a scalar
const
const bio: string
bio
= await
const nodes: {
readonly user: github.User;
readonly clock: Clock;
readonly process: Process;
}

Contains the graph references (grefs) that this program has been given access to.

nodes
.
user: github.handles.User
user
.
github.handles.User.bio: Scalar<string> & (() => Scalar<string>)

The user's bio

bio
// Query a sub-node
const
const name: string
name
= await
const nodes: {
readonly user: github.User;
readonly clock: Clock;
readonly process: Process;
}

Contains the graph references (grefs) that this program has been given access to.

nodes
.
user: github.handles.User
user
.
github.handles.User.repos: github.handles.RepositoryCollection & (() => github.handles.RepositoryCollection)

Collection of Github repositories owned by the user

repos
.
github.handles.RepositoryCollection.one: (args: {
name: string;
}) => github.handles.Repository

Retrieve a single repository by name

one
({
name: string
name
: "docs" }).
github.handles.Repository.description: Scalar<string> & (() => Scalar<string>)

The description of the repository

description
// Use graphQL to get multiple values
const {
const name: string | undefined
name
,
const bio: string | undefined
bio
} = await
const nodes: {
readonly user: github.User;
readonly clock: Clock;
readonly process: Process;
}

Contains the graph references (grefs) that this program has been given access to.

nodes
.
user: github.handles.User
user
.
Field<github.values.User>.$query(query: string): Promise<github.values.User>

Queries the graph starting at node referenced by this g-ref.

@paramquery The query to perform. Must be valid GraphQL query

@returnsThe result of the query. An object with the same shape as the query

@example

This example queries the status and body of the membrane.io website:

const { status, body } = await nodes.http.get({ url: "https://example.com" }).$query("{ status body }")

$query
("{ name bio }")
// Use $query() and graphQL to get all the data you need in one call
const {
const name: string | undefined
name
,
const bio: string | undefined
bio
,
const repos: github.values.RepositoryCollection | undefined
repos
} = await
const nodes: {
readonly user: github.User;
readonly clock: Clock;
readonly process: Process;
}

Contains the graph references (grefs) that this program has been given access to.

nodes
.
user: github.handles.User
user
.
Field<github.values.User>.$query(query: string): Promise<github.values.User>

Queries the graph starting at node referenced by this g-ref.

@paramquery The query to perform. Must be valid GraphQL query

@returnsThe result of the query. An object with the same shape as the query

@example

This example queries the status and body of the membrane.io website:

const { status, body } = await nodes.http.get({ url: "https://example.com" }).$query("{ status body }")

$query
(`
name
bio
repos {
page {
items { name, description }
}
}`)
}

Note that you can only query scalar nodes. When querying objects nodes, you must specify which fields to include by using $query and passing a GraphQL query. Here’s an example that won’t work:

import {
const nodes: {
readonly user: github.User;
readonly clock: Clock;
readonly process: Process;
}

Contains the graph references (grefs) that this program has been given access to.

nodes
} from "membrane"
// This won't work. Which fields do we want from the user node?
const
const user: github.handles.User
user
= await
const nodes: {
readonly user: github.User;
readonly clock: Clock;
readonly process: Process;
}

Contains the graph references (grefs) that this program has been given access to.

nodes
.
user: github.handles.User
user

The above expression doesn’t work because to query an object node you must specify which fields you want to get from it using $query to provide a GraphQL query.

Finally, for convenience, you can omit the outer { and } when using $query. So instead of "{ name bio }" you can just write "name bio".

Querying form the CLI

You can make queries from the command line by using mctl query. For example:

Terminal window
# Query a scalar
$ mctl query 'github:users.one(name:"membrane-io").bio'
# To query an object, pass a GraphQL query
$ mctl query 'github:users.one(name:"membrane-io")' '{ name bio }'

Resolving queries

Pagination

TODO

Query execution

This section explains how Membrane queries work by calling the functions exported from a program. You generally don’t need to think about this, but it’s helpful to understand which functions are called when.

When a query is received for a node, Membrane first combines the gref and the query (if any) into a larger GraphQL query.

If we’re querying { name bio } on the above gref, the actual query that will be executed looks something like this:

{
users {
one(name: "membrane-io") {
# The lines above are derived from the gref
# The lines below come verbatim from the original query
name
bio
}
}
}

Then, to resolve the values, Membrane will invoke these functions in order, which are all exported from

membrane/github:

  1. Root.users
  2. UserCollection.one
  3. User.name
  4. User.bio

The result of each resolver is passed as the obj argument to all child resolvers.

Executing List fields

Resolvers of type List<T> must return an array of values. Membrane will then invoke the resolvers for each item in the array.

For example. If we wanted to get the name and description of all repos, we would typically use the following query:

{
repos {
# The first page of repos
page {
items {
name
description
}
}
# A gref to the next page
next
}
}

Membrane would invoke the resolvers in the following order:

  1. Root.repos
  2. RepositoryCollection.page
  3. RepositoryPage.items
  4. For each item returned by RepositoryPage.items
    1. Repository.name
    2. Repository.description

Gref resolvers

Nodes in the graph are identified by their gref. Why? Because APIs typically use integers IDs, names or UUIDs to identify resources. However, a bare ID is not enough to interact with its corresponding resource. When using an API directly you’d also need to know which endpoints to hit and how to pass the ID.

By using grefs to identity graph nodes, we are able to “encode” all the information needed to interact with something.

All object types have an implicit field aptly named gref which can be queried to get a reference to the node. The gref field, when queried, answers the question “what is the gref that points to this node?”.

For example, we could query user.gref and would get back github:users.one(name:'membrane-io'). That’s not very useful because we already knew that one. However, when querying a list of nodes, including gref in the query can be useful to know the identify of each returned item. We can then use the returned grefs to interact with each item individually.

Membrane’s IDE uses grefs to identify each node. This is what enables you to click on a list item and interact with the corresponding node in a generic way (independent of how each API identifies its resources).

In most cases, you don’t need to write a resolver for this field, but it’s important for when you’re querying a list of nodes and you want to keep track of the identity of each item.