Sorting
The example-app GraphQL API can sort the records returned for each read by the available fields for each model. If you pass the sort
argument, you can control which fields the response is sorted by and in what direction.
await api.post.findMany({sort: {publishedAt: "Descending",},});
1const [result, refresh] = useFindMany(api.post, {2 sort: {3 publishedAt: "Descending",4 },5});6const { data, error, fetching } = result;
1query FindManyPosts($sort: [PostSort!]) {2 posts(sort: $sort) {3 edges {4 node {5 id6 publishedAt7 }8 }9 }10}
{ "sort": { "publishedAt": "Descending" } }
You can sort on findMany
, findFirst
, and maybeFindFirst
read calls using the API Client in Gadget.
Sort order
Records can be ordered in either Descending
or Ascending
order for each field they are sorted on. Descending
order will return the highest values first, while Ascending
order will return the lowest values first. If sorting by multiple fields, each field can have a different order.
Sorting by multiple fields
Records can be sorted by multiple fields by passing multiple sort orders in an array to your API call, like [ { fieldA: "Descending" }, { fieldB: "Ascending" }]
. The first field will be used to order all the records, and then the second field will be used to break ties where records have the same value of the first field. Subsequent sorts will be used to break ties in the second field, etc.
await api.post.findMany({sort: [{ category: "Ascending" }, { publishedAt: "Descending" }],});
const [result, refresh] = useFindMany(api.post, {sort: [{ category: "Ascending" }, { publishedAt: "Descending" }],});const { data, error, fetching } = result;
1query FindManyPosts($sort: [PostSort!]) {2 posts(filter: $filter) {3 edges {4 node {5 id6 publishedAt7 }8 }9 }10}
{ "sort": [{ "category": "Ascending" }, { "publishedAt": "Descending" }] }
Available fields for sorting
Records can be sorted by most fields of their model in either the Ascending
or Descending
direction. The following field types are sortable:
Field types | Notes |
---|---|
string, email, url | Sorted using PostgreSQL's alphanumeric sorting rules |
rich text | Sorted using PostgreSQL's alphanumeric sorting rules on the markdown source text |
number | Sorted by the number's value along the number line |
boolean | Sorted with true higher than false . Descending puts true s first |
date / time | Sorted by the date and time's value, with earlier dates coming before later dates when using Ascending order |
id | Sorted by the ID's numeric value, with lower IDs coming before higher IDs when using Ascending order |
enum | Sorted by the enum values as strings, using PostgreSQL's alphanumeric sorting rules |
json | Sorted by the JSON string representation, using PostgreSQL's alphanumeric sorting rules |
vector | Sorted by a given vector distance operation |
Alphanumeric sorting rules
Gadget uses PostgreSQL's alphanumeric string sorting rules under the hood, which sorts spaces first, then numbers, then upper case letters, then special symbols, then lower case letters, then nulls.
For example, the following list of strings sorts in this order when sorted Ascending
:
1" spaces"2"10-4"3"123"4"Apple"5"Cube"6"___"7"anjou pear"8"banana"9null
Sorting by vector distance
vector fields support vector-specific sort types for returning records in order of distance to a given vector. This implements a nearest neighbor search by querying all your stored vectors to find those that are most similar to an input vector on demand.
To sort by vector distance to a given vector field, pass an object with a to:
input field with the vector to sort by.
If we have a document
model with an embedding
field of type vector, we can sort by the cosine distance to a given vector like so:
1await api.document.findMany({2 sort: {3 embedding: {4 cosineSimilarityTo: [1, 2, 3], // etc5 },6 },7});
1const [result, refresh] = useFindMany(api.document, {2 sort: {3 embedding: {4 cosineSimilarityTo: [1, 2, 3], // etc5 },6 },7});8const { data, error, fetching } = result;
1query FindManyDocuments($sort: [DocumentSort!]) {2 posts(filter: $filter) {3 edges {4 node {5 id6 embedding7 }8 }9 }10}
{ "sort": { "embedding": { "cosineSimilarityTo": [1, 2, 3] } } }
Available vector distance operations
Vectors can be sorted by cosine similarity and L2 (Euclidian) distance to a given vector. Gadget generally recommends using cosine similarity for sorting, as it is more robust to changes in vector magnitude.
- To sort by cosine similarity, pass a sort like
{ fieldName: { cosineSimilarity: { to: [1,2,3] }}}
. Cosine similarity will order by the most similar vectors first, ie cosine similarity descending.
JavaScript1await api.document.findMany({2 sort: {3 embedding: {4 cosineSimilarityTo: [1, 2, 3],5 },6 },7});
- To sort by L2 distance, pass a sort like
{ fieldName: { l2Distance: { to: [1,2,3] }}}
. L2 distance will order by the closest vectors first, ie L2 distance ascending.
JavaScript1await api.document.findMany({2 sort: {3 embedding: {4 l2DistanceTo: [1, 2, 3],5 },6 },7});
Sorting by least similar
Vectors can be sorted for least similarity by using one of the vector distance sorts with the order: "Ascending"
option. This will return the least similar vectors first.
1await api.document.findMany({2 sort: {3 embedding: {4 cosineSimilarityTo: [1, 2, 3], // etc5 order: "Ascending",6 },7 },8});
1const [result, refresh] = useFindMany(api.document, {2 sort: {3 embedding: {4 cosineSimilarityTo: [1, 2, 3], // etc5 order: "Ascending",6 },7 },8});9const { data, error, fetching } = result;
1query FindManyDocuments($sort: [DocumentSort!]) {2 posts(filter: $filter) {3 edges {4 node {5 id6 embedding7 }8 }9 }10}
{"sort": { "embedding": { "cosineSimilarityTo": [1, 2, 3], "order": "Ascending" } }}
Unsortable fields
Currently, Gadget does not support sorting records by a field of the following types:
- file
- encrypted string
- role list
- password
- has many
- has one
- has many through
Filtering
The example-app GraphQL API can filter records when reading to only the records matching certain conditions. If you pass a filter
argument to a read API call, only records that match the filter will be returned.
await api.post.findMany({filter: {isPublished: { equals: true },},});
1const [result, refresh] = useFindMany(api.post, {2 filter: {3 isPublished: {4 equals: true,5 },6 },7});8const { data, error, fetching } = result;
1query FindManyPosts($filter: [PostFilter!]) {2 posts(filter: $filter) {3 edges {4 node {5 id6 isPublished7 }8 }9 }10}
{ "filter": { "isPublished": { "equals": true } } }
You can filter on findMany
, findFirst
, and maybeFindFirst
read requests using the API Client in Gadget.
Available fields for filtering
Records can be filtered by most fields of their model, and the type of each model field determines what kind of filters are available in the API. For each field, Gadget uses a specific type for filtering that field type:
Field types | Filter GraphQL Type |
---|---|
string, rich text, email, url | StringFilter |
number | FloatFilter and IntFilter |
boolean | BooleanFilter |
date / time | DateTimeFilter |
id | IDFilter |
enum | SingleEnumFilter and MultiEnumFilter |
json | JSONFilter |
vector | VectorFilter |
record state | StateFilter |
belongs to | IDFilter |
Each model then has a filter-specific GraphQL type that details the types of filters available on the selected model. For example, if there is a Post
model to capture blog post records, there will also be a generated PostFilter
GraphQL type. This filter type can be viewed in the API Reference or API Playground.
Examine the filter typing of your app's API by checking the documentation in the API Playground. All filter types will be visible for all fields on your app's models.
Combining filters
Records can also be filtered by multiple different fields. If you want to combine filters using boolean logic, nest them under the AND
, OR
, or NOT
keys of a parent filter.
await api.post.findMany({filter: {OR: [{ isPublished: { equals: true } }, { wordCount: { greaterThan: 500 } }],},});
1const [result, refresh] = useFindMany(api.post, {2 filter: {3 OR: [4 {5 isPublished: { equals: true },6 },7 {8 wordCount: { greaterThan: 500 },9 },10 ],11 },12});13const { data, error, fetching } = result;
1query FindManyPosts($filter: [PostFilter!]) {2 posts(filter: $filter) {3 edges {4 node {5 id6 isPublished7 }8 }9 }10}
1{2 "filter": {3 "OR": [4 { "isPublished": { "equals": true } },5 { "wordCount": { "greaterThan": 500 } }6 ]7 }8}
Filters can be nested deeply by passing multiple levels of boolean condition filters.
For example, we can fetch orders from a Shopify Order model where the total is between $100 and $200, and the order is either paid or refunded:
1api.shopifyOrder.findMany({2 filter: {3 AND: [4 {5 AND: [6 { totalPrice: { greaterThan: 100 } },7 { totalPrice: { lessThan: 200 } },8 ],9 },10 {11 OR: [12 { financialStatus: { equals: "paid" } },13 { financialStatus: { equals: "refunded" } },14 ],15 },16 ],17 },18});
1const [{ data, error, fetching }, refresh] = useFindMany(api.shopifyOrder, {2 filter: {3 AND: [4 {5 AND: [6 { totalPrice: { greaterThan: 100 } },7 { totalPrice: { lessThan: 200 } },8 ],9 },10 {11 OR: [12 { financialStatus: { equals: "paid" } },13 { financialStatus: { equals: "refunded" } },14 ],15 },16 ],17 },18});
1query FindManyShopifyOrders($filter: [ShopifyOrderFilter!]) {2 shopifyOrders(filter: $filter) {3 edges {4 node {5 id6 totalPrice7 financialStatus8 }9 }10 }11}
1{2 "filter": [3 {4 "AND": [5 { "totalPrice": { "greaterThan": 100 } },6 { "totalPrice": { "lessThan": 200 } }7 ]8 },9 {10 "OR": [11 { "financialStatus": { "equals": "paid" } },12 { "financialStatus": { "equals": "refunded" } }13 ]14 }15 ]16}
Filter types
There are many different filter types available in Gadget. Each filter type corresponds to one or more field types.
StringFilter
The StringFilter type provides a filter definition for the following field types: string, rich text, email, url
The StringFilter type has the following fields:
StringFilter fields1equals: String2notEquals: String3isSet: Boolean4in: [String]5notIn: [String]6lessThan: String7lessThanOrEqual: String8greaterThan: String9greaterThanOrEqual: String10startsWith: String
An example of a StringFilter being used to filter students by name:
JavaScript1// get all students with the firstName "Taylor"2await api.student.findMany({3 filter: {4 firstName: {5 equals: "Taylor",6 },7 },8});
The startsWith
filter field is unique to StringFilters, and checks to see if a string begins with the provided value. For example:
JavaScript1// get all blog posts that have titles starting with "How to"2await api.post.findMany({3 filter: {4 title: {5 startsWith: "How to",6 },7 },8});
FloatFilter and IntFilter
The FloatFilter and IntFilter types provide filtering for the number field type. The FloatFilter type is used for fields that have a decimal value, and the IntFilter type is used for fields that have an integer value.
Note: The Float scalar type represents signed double-precision fractional values as specified by IEEE 754.
Both the FloatFilter and IntFilter types have the following fields:
FloatFilter and IntFilter fields1equals: Float2notEquals: Float3isSet: Boolean4in: [Float]5notIn: [Float]6lessThan: Float7lessThanOrEqual: Float8greaterThan: Float9greaterThanOrEqual: Float
An example of a FloatFilter being used to filter blog posts by word count:
JavaScript1// get all blog posts that are over 500 words2await api.posts.findMany({3 filter: {4 wordCount: {5 greaterThan: 500,6 },7 },8});
BooleanFilter
The BooleanFilter type provides filtering for the boolean field type. You can filter to records that have the boolean set to true
, false
, or filter to records that have a value set at all.
BooleanFilter fieldsequals: FloatnotEquals: FloatisSet: Boolean
An example of a BooleanFilter being used to filter blog posts by isPublished = true
:
JavaScript1// get all blog posts that are published2await api.posts.findMany({3 filter: {4 isPublished: {5 equals: true,6 },7 },8});
An example of a BooleanFilter being used to filter blog posts by if the isPublished
field is set at all, to either true
or false
:
JavaScript1// get all blog posts that have a value for the `isPublished` boolean (either true or false)2await api.posts.findMany({3 filter: {4 isPublished: {5 isSet: true,6 },7 },8});910// get all blog posts that have `isPublished` set to null11await api.posts.findMany({12 filter: {13 isPublished: {14 isSet: false,15 },16 },17});
DateTimeFilter
The DateTimeFilter type provides a filter definition for the date / time field type
DateTimeFilters need to have values provided as a date-time string in UTC, for example, 2007-12-03T10:15:30Z
. To get this value from a JavaScript Date object, you can call .toUTCString()
on your Date object: new Date().toUTCString()
;
DateTime objects are only stored to the nearest millisecond if you are filtering with smaller increments of time such as nanoseconds, the value will be truncated to only include milliseconds.
The DateTimeFilter type has the following fields:
DateTimeFilter fields1equals: DateTime2notEquals: DateTime3isSet: Boolean4in: [DateTime]5notIn: [DateTime]6lessThan: DateTime7lessThanOrEqual: DateTime8greaterThan: DateTime9greaterThanOrEqual: DateTime10before: DateTime11after: DateTime
The before
field is the same as the lessThan
field, and the after
field is the same as the greaterThan
field. The before
and after
options have been added for better readability and understanding when talking about comparing date / time fields.
An example of a DateTimeFilter being used to get all blog posts written in the past week:
JavaScript1// get all blog posts written last week2const oneWeekAgo = new Date(Date.now() - 604800000);3await api.post.findMany({4 filter: {5 createdAt: {6 after: oneWeekAgo,7 },8 },9});
IDFilter
The IDFilter type provides a filter definition for the id and the belongs to field type.
The IDFilter type has the following fields:
IDFilter fields1equals: GadgetID2notEquals: GadgetID3isSet: Boolean4in: [GadgetID]5notIn: [GadgetID]6lessThan: GadgetID7lessThanOrEqual: GadgetID8greaterThan: GadgetID9greaterThanOrEqual: GadgetID
An example of an IDFilter being used to filter by a list of students:
JavaScript1// get all students with ids that match 1, 2, or 32await api.student.findMany({3 filter: {4 id: {5 in: [1, 2, 3],6 },7 },8});
Filtering on belongs to relationships
You can filter one model by its belongs to relationship to another model using the other model's ID. For example, if we have a comment
model that belongs to a parent post
model, we can fetch all the comments for a given post
using a filter on the comment
model's post
field:
JavaScript1// get all comments belonging to the post with id 422const commentsForPost = await api.comment.findMany({3 filter: {4 post: {5 equals: 42,6 },7 },8});
Filtering on belongs to relationships supports the other filtering operators as well, so you can also filter out nulls with isSet
, or fetch children for lists of parents all at once with the in
operator:
JavaScript1// get all comments that don't have a post2const commentsForPost = await api.comment.findMany({3 filter: {4 post: {5 isSet: false,6 },7 },8});910// get all comments for posts 10, 11 and 1211const commentsForPost = await api.comment.findMany({12 filter: {13 post: {14 in: [10, 11, 12],15 },16 },17});
SingleEnumFilter
The SingleEnumFilter type provides a filter definition for the enum field type when the selection of multiple options is not allowed.
The SingleEnumFilter type has the following fields:
SingleEnumFilter fieldsisSet: Booleanequals: StringnotEquals: Stringin: [String]
An example of a SingleEnumFilter used to fetch all JIRA tickets that are not in the backlog:
JavaScript1// get all tickets that do not have a status of "backlog"2await api.tickets.findMany({3 filter: {4 status: {5 notEquals: "backlog",6 },7 },8});
MultiEnumFilter
The MultiEnumFilter type provides a filter definition for the enum field type when the selection of multiple options is allowed.
The MultiEnumFilter type has the following fields:
MultiEnumFilter fieldsisSet: Booleanequals: [String]notEquals: [String]contains: [String]
The contains
filter field is unique to the MultiEnumFilter type. If an enum field allows for the selection of multiple values, contains
can be used to match against a combination of values in a single query. contains
checks to see if all provided filter values are present, but does not perform an exact match - for exact matching, use equals
.
For example, if an enum field is used to keep track of the means of communication a Client has signed up for, it might contain the following options: "Sms", "Email", "Phone". Multiple options can be selected at once. If I want to filter all Clients that allow for "Sms" and "Email" communication, I could use the following filter:
Example of a contains filter on an Enum field with multiple selections enabledJavaScript1await api.client.findMany({2 filter: {3 communication: {4 contains: ["Sms", "Email"],5 },6 },7});
This will return all Client records that have both the "Sms" and "Email" options selected. Because this is not an exact match, this will also include Client records that have all 3 options selected.
JSONFilter
The JSONFilter type provides a filter definition for the json field type.
The JSONFilter type has the following fields:
JSONFilter fields1isSet: Boolean2equals: JSON3in: [JSON]4notIn: [JSON]5notEquals: JSON6matches: JSON
The matches
filter field is unique to the JSONFilter type and allows you to perform a partial match on json fields. For example:
JavaScript1await api.systemSetup.findMany({2 filter: {3 configuration: {4 matches: { foo: "bar" },5 },6 },7});
This will return all of the System Setup model's records that have the name/value pair { foo: "bar" }
in the configuration field. Examples of this include configuration: { foo: "bar" }
as well as configuration: { foo: "bar", fizz: "buzz" }
. Both of these records would be returned by the above query.
VectorFilter
The VectorFilter type provides filtering on vector fields storing lists of floats (vectors) in the database. Vector filters can be used to return only records that have a vector field similar to an input vector.
The VectorFilter type has the following fields:
JSONFilter fields1isSet: Boolean2equals: Vector3l2Distance: {4 to: vector5 lessThan: Float6 lessThanOrEqual: Float7 greaterThan: Float8 greaterThanOrEqual: Float9}10cosineSimilarity: {11 to: vector12 lessThan: Float13 lessThanOrEqual: Float14 greaterThan: Float15 greaterThanOrEqual: Float16}
For example, to filter to only records that have a vector field named embedding
set, you can use the isSet
filter:
JavaScript1await api.someModel.findMany({2 filter: {3 embedding: {4 isSet: true,5 },6 },7});
Vector distance filters
vector fields can be filtered to only return records with a vector that is similar to an input vector. These filter types
accept an input vector in the to
field, and then a distance or similarity threshold. Each stored vector in your model's field is compared
to the input vector to compute a distance or similarity, and then only the vectors which have a distance or similarity passing the input
threshold are returned.
For example, to return only vectors that have a cosine similarity of at least 0.8
to a given input vector, you can use the cosineSimilarity
filter:
JavaScript1await api.someModel.findMany({2 filter: {3 embedding: {4 cosineSimilarity: {5 to: [1, 2, 3],6 greaterThan: 0.8,7 },8 },9 },10});
This will return records from the someModel
model that have a vector stored in the embedding
field that is similar to the input vector [1, 2, 3]
.
When filtering vectors, the input vector in the to
field must have the same dimensions (length) as the stored vector. Trying to filter
by a vector with different dimensions will throw an error and not return any data.
To return only vectors that have an L2 (Euclidian) distance of less than 1
to a given input vector, you can use the l2Distance
filter:
JavaScript1await api.someModel.findMany({2 filter: {3 embedding: {4 l2Distance: {5 to: [1, 0, 1],6 lessThanOrEqualTo: 1,7 },8 },9 },10});
When searching for vectors that are close to an input vector, it is recommended to use the cosineSimilarity
sort, instead of the
filter. The closest vectors to a given input can be found by sorting by cosineSimilarity
descending, which will return the closest
vectors first.
See more details in the Sorting by vector distance section.
StateFilter
The StateFilter type provides a filter definition for the record state field type. This field type is currently only in use on the Shopify Shop model that is added to your Gadget project when you set up a Shopify connection.
The StateFilter type has the following fields:
StateFilter fieldsisSet: BooleaninState: String
The inState
filter field is unique to the StateFilter type. You need to use a dot-separated string of states to filter with inState
, for example:
Example of an inState filter used on the Shopify Shop modelJavaScriptawait api.shopifyShop.findMany({filter: {state: { inState: "created.installed" },},});
Filtering nulls with isSet
All the filter types allow you to filter for records where the value is set to something or set to null
using the isSet
filter. { isSet: true }
will only return records where the value is set (is not null
), and { isSet: false }
will only return records where the value is null
.
For example, we could filter a Post model to only find records where the publishDate
is set to something:
Example of an isSet filter used on the Post modelJavaScriptawait api.post.findMany({filter: {publishDate: { isSet: true },},});
Or filter the Shopify Order model to find records where the customMetafield
field is currently null
:
Example of an isSet filter used on the ShopifyOrder modelJavaScriptawait api.shopifyOrder.findMany({filter: {customMetafield: { isSet: false },},});
Unfilterable fields
Currently, Gadget doesn't support filtering for the following field types:
- file
- encrypted string
- password
- has many
- has one
- has many through
Filtering models by a related model's properties
You cannot filter a model by the properties of a related model.
If you have two related models, such as Post
and Author
for a blog app where each Author
has many Post
and you want to get the published blog posts for authors who live in Canada (filter Author
by Posts
's "published" property), you need to write two separate queries:
Filter across relationshipsJavaScript1const canadianAuthors = await api.author.findMany({2 filter: {3 country: { equals: "Canada" },4 },5});6const canadianAuthorIds = canadianAuthors.map((author) => author.id);7const posts = await api.post.findMany({8 filter: {9 AND: [10 { authorId: { in: canadianAuthorIds } },11 { isPublished: { equals: true } },12 ],13 },14});
You can filter related models by the related model's attributes using GraphQL. This is not yet supported in the JavaScript client! For example, you could filter for Author
who live in Canada and include their blog Posts
that are longer than 500 words:
GraphQL1query {2 authors(filter: { country: { equals: "Canada" } }) {3 edges {4 node {5 id6 name7 country8 posts(filter: { wordCount: { greaterThan: 500 } }) {9 edges {10 node {11 id12 title13 wordCount14 }15 }16 }17 }18 }19 }20}
This would return all authors along with their blog posts that have a word count greater than 500.
There is one exception to this rule: you can filter from the belongs to side of a relationship on the id of the related model. This means you can use isSet
, equals
, or any of the other filter operations that would be valid on a id.
For example, if you simply wanted to filter for published Posts
s that have Author
s without filtering on the Author
's country you could use the following query:
Filter across relationships on the "belongsTo" side of the relationshipJavaScript1// this will only apply the `author` filter to the ID field on author!2const posts = await api.post.findMany({3 filter: {4 isPublished: { equals: true },5 author: { isSet: true },6 },7});