Computing in Gadget with Gelly

Gadget supports a second tool for extending an application: a data access language named Gelly. Gelly is a GraphQL superset that adds support for computations, sorting, filtering, and aggregating, while maintaining the easy and nestable data access patterns that GraphQL makes easy. Gelly snippets enable Computed fields and Conditional Permissions inside Gadget.

Gelly snippets look like this:

GELLY
1fragment Details on Product {
2 # select the name field
3 name
4
5 # select a field named inStock, which is true if the
6 # inventory count is greater than 0
7 inStock: inventoryCount > 0
8
9 # sort the returned list of Product by the id field descending
10 [order by id desc]
11
12 # return some fields the first three variants of each product,
13 # sorted by most recently created
14 variants {
15 id
16 name
17 price
18 [limit 3 order by createdDate desc]
19 }
20
21 # and return an aggregate count of the images for the product
22 count(images.id)
23}

Why a different language?

Plain old JavaScript code works well for adding functionality to Gadget applications when implementing behavior, but some parts of applications have different needs. Gelly computations are declarative such that Gadget can pre-compute or re-compute the values of Gelly snippets very efficiently across a high number of rows. This means you don't need to determine and store a value each time a record changes in a String or Number, and instead you can create a Computed field that's easier to change and better performing.

Currently, Gadget compiles Gelly into high performance SQL to execute inside the database with a custom caching layer on top, which makes Gelly fast while remaining consistent. Gadget does this in order to power features like filtering or sorting on a computed field, and conditional permissions that decide if a row is visible using data from related rows. Neither of these is possible at scale if the computation is defined in JavaScript because Gadget would have to run the computation for every row in the database to know which ones to return, which can be too slow at scale.

Gelly for SQL lovers

Gelly is similar to, and inspired by SQL! A Gelly query is also an up front, declarative ask for some data based on the relational algebra, but it's different in that it allows relationship traversal, fragments, and more ergonomic expressions. Gelly eliminates a few annoyances from plain old SQL like having to manually specify join conditions all the time, having to execute multiple queries or complicated json_agg functions to retrieve related data, and plain old quality of life stuff like not requiring trailing commas or a super specific statement order.

Gelly for GraphQL lovers

Gelly is very similar to GraphQL, but extends it with the ability to do a bunch of fancy stuff normal GraphQL APIs can't. Gelly snippets can contain arbitrary computations that build new expressions with normal-looking code, as well as universally available commands to filter, sort, or aggregate a list of data.

Computed Fields

Computed fields are read-only fields defined by a Gelly snippet. A Computed field is accessed the same ways as any other field through the API. Gadget gaurantees the value to be up to date as input data referenced in the computed field's definition changes, and Gadget takes responsibility for efficiently computing and caching the value. You don't have to spend time worrying about counter caches, TTLs or refresh intervals, query optimzation, or input row counts. Gadget implements Gelly with on-demand SQL queries for lightweight Gelly snippets and incremental dataflow recomputation for heavy weight Gelly snippets behind the scenes.

Computed fields are defined using a Gelly field fragment. A field fragment defines one named field on a model and an expression to produce the value for each row.

An example field fragment for a Computed might define the fullName field for a Customer model, which might comprise the first name plus a space plus a last name:

GELLY
field fullName on Customer {
firstName + ' ' + lastName
}

Another Computed might define the count of all the comments on a BlogPost model using a count of a HasMany relationship:

GELLY
field commentCount on BlogPost {
count(comments.id)
}

Note that Computed fields are defined by only one expression, and can't produce more than one value for one row. This is why Computed fields use Field Fragments and not normal Fragments. You can't use a normal Fragment to define a computed field like so:

GELLY
# won't work, must use a Field Fragment, not a normal Fragment
fragment CommentCount on BlogPost {
commentCount: count(comments.id)
otherField: upper(title)
}

Examples

Produce a fullName field that concatenates the firstName and lastName string for a Customer model:

GELLY
field fullName on Customer {
firstName + ' ' + lastName
}