## Sorting  The **openai-screenwriter-tutorial-v2** GraphQL API can sort the records returned by the sortable fields for each model. If you pass the `sort` argument to a find call, you can pass the fields and directions to sort by. ```typescript await api.post.findMany({ sort: { publishedAt: "Descending", }, }); ``` ```tsx const [result, refresh] = useFindMany(api.post, { sort: { publishedAt: "Descending", }, }); const { data, error, fetching } = result; ``` ```graphql query FindManyPosts($sort: [PostSort!]) { posts(sort: $sort) { edges { node { id publishedAt } } } } ``` ```json { "sort": { "publishedAt": "Descending" } } ``` You can sort on `findMany`, `findFirst`, and `maybeFindFirst` read calls using the API Client in Forgelivo. ### 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 as the first field. Subsequent sorts will be used to break ties in the second field, etc. ```typescript await api.post.findMany({ sort: [{ category: "Ascending" }, { publishedAt: "Descending" }], }); ``` ```tsx const [result, refresh] = useFindMany(api.post, { sort: [ { category: "Ascending" }, { publishedAt: "Descending" } ] }); const { data, error, fetching } = result; ``` ```graphql query FindManyPosts($sort: [PostSort!]) { posts(filter: $filter) { edges { node { id publishedAt } } } } ``` ```json { "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 Postgres's alphanumeric sorting rules | | rich text | Sorted using Postgres'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 Postgres's alphanumeric sorting rules | | json | Sorted by the JSON string representation, using Postgres's alphanumeric sorting rules | | vector | Sorted by a given vector distance operation | To sort by a field, that field must have the `Index for filter and sort` option enabled in the field settings. Fields with this option disabled can't be sorted on in your API. ### Alphanumeric sorting rules  Forgelivo uses Postgres'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`: ```markdown " spaces" "10-4" "123" "Apple" "Cube" "___" "anjou pear" "banana" null ``` ### 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: ```typescript await api.document.findMany({ sort: { embedding: { cosineSimilarityTo: [1, 2, 3], // etc }, }, }); ``` ```tsx const [result, refresh] = useFindMany(api.document, { sort: { embedding: { cosineSimilarityTo: [1, 2, 3] // etc } } }); const { data, error, fetching } = result; ``` ```graphql query FindManyDocuments($sort: [DocumentSort!]) { posts(filter: $filter) { edges { node { id embedding } } } } ``` ```json { "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. Forgelivo 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. ```typescript await api.document.findMany({ sort: { embedding: { cosineSimilarityTo: [1, 2, 3], }, }, }); ``` * 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. ```typescript await api.document.findMany({ sort: { embedding: { l2DistanceTo: [1, 2, 3], }, }, }); ``` #### 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. ```typescript await api.document.findMany({ sort: { embedding: { cosineSimilarityTo: [1, 2, 3], // etc order: "Ascending", }, }, }); ``` ```tsx const [result, refresh] = useFindMany(api.document, { sort: { embedding: { cosineSimilarityTo: [1, 2, 3], // etc order: "Ascending" } } }); const { data, error, fetching } = result; ``` ```graphql query FindManyDocuments($sort: [DocumentSort!]) { posts(filter: $filter) { edges { node { id embedding } } } } ``` ```json { "sort": { "embedding": { "cosineSimilarityTo": [1, 2, 3], "order": "Ascending" } } } ``` #### Selecting vector distance calculations as fields  In addition to sorting by vector distance, you can select vector distance calculations as fields in your queries to retrieve the actual similarity scores or distance values for each record. For any vector field, you can select `{fieldName}CosineSimilarityTo` or `{fieldName}L2DistanceTo` fields. Read more about selecting vector distance calculations in the [Building AI Apps guide](https://docs.forgelivo.com/guides/plugins/openai/building-ai-apps#selecting-vector-distance-calculations-as-fields). ### Unsortable fields  Currently, Forgelivo does not support sorting records by fields of the following types: * file * encrypted string * password * has many * has one * has many through * computed ### Sorting by a related model's fields [Framework v1.4.0+](https://docs.forgelivo.com/guides/gadget-framework)  You can sort a query on a model by the fields of a related model by traversing the relationship. This allows you to order records based on values stored in related records rather than just fields on the model itself. Sorting by related model fields is only supported for belongs to relationships. has many, has one, and has many through relationships do not support sorting because they can reference multiple related records, making the sort order ambiguous. #### Sorting across belongs to relationships  You can sort on any sortable field of a related model through a belongs to relationship. For example, if you have a blog application with `post` and `author` models, where `post` belongs to `author`, you can sort posts by the author's name: ```typescript await api.post.findMany({ sort: { author: { name: "Ascending", }, }, }); ``` ```tsx const [result, refresh] = useFindMany(api.post, { sort: { author: { name: "Ascending" } } }); const { data, error, fetching } = result; ``` ```graphql query FindManyPosts($sort: [PostSort!]) { posts(sort: $sort) { edges { node { id title author { name } } } } } ``` ```json { "sort": { "author": { "name": "Ascending" } } } ``` The relationship field on the queried model, `author`, is used to access the related model's `name` field for sorting. #### Sorting across multiple relationships  Relationship traversals in a sort can be chained up to a maximum of 2 steps. For example, if `post` belongs to `author` and `author` belongs to `company`, you can sort posts by the author's company name: ```typescript // Sort posts by the author's company name const posts = await api.post.findMany({ sort: { author: { company: { name: "Ascending", }, }, }, }); ``` Attempting to sort by a field more than 2 relationship hops away will result in an error. #### Combining relationship sorts with other sorts  You can combine relationship sorts with regular field sorts and use multiple sort fields to break ties where records have the same value as the first field: ```typescript // Sort posts by author name, then by publish date const posts = await api.post.findMany({ sort: [{ author: { name: "Ascending" } }, { publishedAt: "Descending" }], }); ``` Records with `null` values in the relationship (no related record) will be sorted according to Postgres's null sorting rules. By default, nulls appear last when sorting in ascending order and first when sorting in descending order. ## Filtering  The **openai-screenwriter-tutorial-v2** 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. ```typescript await api.post.findMany({ filter: { isPublished: { equals: true }, }, }); ``` ```tsx const [result, refresh] = useFindMany(api.post, { filter: { isPublished: { equals: true } }, }); const { data, error, fetching } = result; ``` ```graphql query FindManyPosts($filter: [PostFilter!]) { posts(filter: $filter) { edges { node { id isPublished } } } } ``` ```json { "filter": { "isPublished": { "equals": true } } } ``` You can filter on `findMany`, `findFirst`, and `maybeFindFirst` read requests using the API Client in Forgelivo. ### 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, Forgelivo uses a specific type for filtering that field type: | Field types | Filter GraphQL Type | | --- | --- | | string, rich text, email, url | | | number | and | | boolean | | | date / time | | | id | | | enum | and | | json | | | vector | | | record state | | | role list | | | belongs to | | | has one | | | has many | | | has many through | | 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. To filter by a field, that field must have the `Index for filter and sort` option enabled in the field settings. Fields with this option disabled can't be filtered on in your API. ### 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. ```typescript await api.post.findMany({ filter: { OR: [{ isPublished: { equals: true } }, { wordCount: { greaterThan: 500 } }], }, }); ``` ```tsx const [result, refresh] = useFindMany(api.post, { filter: { OR: [ { isPublished: { equals: true } }, { wordCount: { greaterThan: 500 } } ] }, }); const { data, error, fetching } = result; ``` ```graphql query FindManyPosts($filter: [PostFilter!]) { posts(filter: $filter) { edges { node { id isPublished } } } } ``` ```json { "filter": { "OR": [ { "isPublished": { "equals": true } }, { "wordCount": { "greaterThan": 500 } } ] } } ``` 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: ```typescript api.shopifyOrder.findMany({ filter: { AND: [ { AND: [ { totalPrice: { greaterThan: 100 } }, { totalPrice: { lessThan: 200 } }, ], }, { OR: [ { financialStatus: { equals: "paid" } }, { financialStatus: { equals: "refunded" } }, ], }, ], }, }); ``` ```tsx const [{ data, error, fetching }, refresh] = useFindMany(api.shopifyOrder, { filter: { AND: [ { AND: [{ totalPrice: { greaterThan: 100 } }, { totalPrice: { lessThan: 200 } }]}, { OR: [{ financialStatus: { equals: "paid"} }, { financialStatus: { equals: "refunded" } }] } ] }, }); ``` ```graphql query FindManyShopifyOrders($filter: [ShopifyOrderFilter!]) { shopifyOrders(filter: $filter) { edges { node { id totalPrice financialStatus } } } } ``` ```json { "filter": [ { "AND": [ { "totalPrice": { "greaterThan": 100 } }, { "totalPrice": { "lessThan": 200 } } ] }, { "OR": [ { "financialStatus": { "equals": "paid" } }, { "financialStatus": { "equals": "refunded" } } ] } ] } ``` ### Filter types  There are many different filter types available in Forgelivo. Each filter type corresponds to one or more field types. #### Filtering on string and string-like fields  Fields using the string type or other string-like field types like rich text, email, url can be filtered using string filters. For example, you can filter down to records where a string is equal to a given string: ```typescript await api.student.findMany({ filter: { firstName: { equals: "Taylor", }, }, }); ``` Or filter to records where the stored string starts with a given string prefix: ```typescript await api.post.findMany({ filter: { title: { startsWith: "How to", }, }, }); ``` The full list of supported filters on string and string-like fields are are: * * * * * * * * * * ##### `equals` string filtering  The `equals` filter key is used to filter down to records where the string field is equal to a given string. `equals` does a case-sensitive match to check if the incoming string is exactly equal to the stored string, and will only return records where that is true. ```typescript await api.widget.findMany({ filter: { name: { equals: "gizmo", }, }, }); ``` ##### `notEquals` string filtering  The `notEquals` filter key is used to filter down to records where the string field is set to anything other than a given string. `notEquals` does a case-sensitive match to check if the incoming string is exactly equal to the stored string, and will only return records where that is false. ```typescript await api.widget.findMany({ filter: { name: { notEquals: "gizmo", }, }, }); ``` ##### `isSet` string filtering  The `isSet` filter key is used to filter down to records where the string field is set to anything other than `null`. `isSet` will only return records where the value is not `null`. ```typescript await api.widget.findMany({ filter: { name: { isSet: true, }, }, }); ``` `isSet` can also be used to check for null by passing `isSet: false`. ```typescript await api.widget.findMany({ filter: { name: { isSet: false, }, }, }); ``` ##### `in` string filtering  The `in` filter key is used to filter down to records where the string field is exactly equal to one of the provided strings. `in` does a case-sensitive exact comparison between each of the provided strings to the string stored in the database, and will return records where the string field is equal to any of the provided strings. The `in` filter will never return records where the stored string is null. ```typescript await api.widget.findMany({ filter: { name: { in: ["gizmo", "gadget"], }, }, }); ``` ##### `notIn` string filtering  The `notIn` filter key is used to filter down to records where the string field is not equal to any of the provided strings. `notIn` does a case-sensitive exact comparison between each of the provided strings to the string stored in the database, and will return records where the string field is not equal to any of the provided strings. `notIn` will also return records where the stored string is null. ```typescript await api.widget.findMany({ filter: { name: { notIn: ["gizmo", "gadget"], }, }, }); ``` ##### `startsWith` string filtering  The `startsWith` filter key is used to filter down to records where the string field starts with the given string. This filter is case-sensitive and will return records where the string field begins with the specified substring. `startsWith` will never return records where the string field is set to null. ```typescript await api.widget.findMany({ filter: { name: { startsWith: "giz", }, }, }); ``` ##### `lessThan` and `lessThanOrEqual` string filtering  The `lessThan` filter key is used to filter down to records where the string field is lexicographically less than a given string. Lexicographically less means that the string comes before the given string in dictionary order, based on the Unicode values of the characters. For example, "apple" is lexicographically less than "banana", and "abc" is lexicographically less than "abd". ```typescript await api.widget.findMany({ filter: { name: { lessThan: "gizmo", }, }, }); ``` Similarly, the `lessThanOrEqual` filter key is used to filter down to records where the string field is lexicographically less than or equal to a given string. ```typescript await api.widget.findMany({ filter: { name: { lessThanOrEqual: "gizmo", }, }, }); ``` ##### `greaterThan` and `greaterThanOrEqual` string filtering  The `greaterThan` filter key is used to filter down to records where the string field is lexicographically greater than a given string. Lexicographically greater means that the string comes after the given string in dictionary order, based on the Unicode values of the characters. For example, "banana" is lexicographically greater than "apple", and "abd" is lexicographically greater than "abc". ```typescript await api.widget.findMany({ filter: { name: { greaterThan: "gizmo", }, }, }); ``` Similarly, the `greaterThanOrEqual` filter key is used to filter down to records where the string field is lexicographically greater than or equal to a given string. ```typescript await api.widget.findMany({ filter: { name: { greaterThanOrEqual: "gizmo", }, }, }); ``` ##### Case-insensitive filtering  Currently, Forgelivo has no filters for strings that do a case-insensitive comparison between the stored value and provided value. You can work around this by storing a second copy of the string on your model, and lowercasing it upon storage and upon filtering: ```typescript export const run: ActionRun = async ({ params, record }) => { applyParams(record, params); // store the lowercase version of the name as well for filtering later record.lowercaseName = record.name?.toLowerCase(); await save(record); }; ``` Then, you can filter on the lowercase name: ```typescript await api.widget.findMany({ filter: { lowercaseName: { startsWith: "Giz".toLowerCase(), }, }, }); ``` If native case-insensitivity is an important feature for you, please reach out to us in our [Discord](https://ggt.link/discord)! #### Filtering on number fields  The number field type supports filtering on numeric fields. Numbers can be filtered by null-ness, equality, and by range. For example, we can filter a post model by the `wordCount` number field to get all posts over 500 words: ```typescript await api.posts.findMany({ filter: { wordCount: { greaterThan: 500, }, }, }); ``` Under the hood, your API generates different filter types for `` fields marked as integers or floats. 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. The Float scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). The full list of supported filters on number fields are: * * * * * * * * * ##### `equals` number filtering  The `equals` filter key is used to filter down to records where the number field is equal to a given number. `equals` does an exact match to check if the incoming number is exactly equal to the stored number, and will only return records where that is true. ```typescript await api.widget.findMany({ filter: { quantity: { equals: 10, }, }, }); ``` ##### `notEquals` number filtering  The `notEquals` filter key is used to filter down to records where the number field is set to anything other than a given number. `notEquals` does an exact match to check if the incoming number is exactly equal to the stored number, and will only return records where that is false. ```typescript await api.widget.findMany({ filter: { quantity: { notEquals: 10, }, }, }); ``` ##### `isSet` number filtering  The `isSet` filter key is used to filter down to records where the number field is set to anything other than `null`. `isSet` will only return records where the value is not `null`. ```typescript await api.widget.findMany({ filter: { quantity: { isSet: true, }, }, }); ``` `isSet` can also be used to check for null by passing `isSet: false`. ```typescript await api.widget.findMany({ filter: { quantity: { isSet: false, }, }, }); ``` ##### `in` number filtering  The `in` filter key is used to filter down to records where the number field is exactly equal to one of the provided numbers. `in` does an exact comparison between each of the provided numbers to the number stored in the database, and will return records where the number field is equal to any of the provided numbers. The `in` filter will never return records where the stored number is null. ```typescript await api.widget.findMany({ filter: { quantity: { in: [10, 20], }, }, }); ``` ##### `notIn` number filtering  The `notIn` filter key is used to filter down to records where the number field is not equal to any of the provided numbers. `notIn` does an exact comparison between each of the provided numbers to the number stored in the database, and will return records where the number field is not equal to any of the provided numbers. `notIn` will also return records where the stored number is null. ```typescript await api.widget.findMany({ filter: { quantity: { notIn: [10, 20], }, }, }); ``` ##### `lessThan` and `lessThanOrEqual` number filtering  The `lessThan` number key is used to filter down to records where the number field is less than a given number. ```typescript await api.widget.findMany({ filter: { quantity: { lessThan: 10, }, }, }); ``` Similarly, the `lessThanOrEqual` filter key is used to filter down to records where the number field is less than or equal to a given number. ```typescript await api.widget.findMany({ filter: { quantity: { lessThanOrEqual: 10, }, }, }); ``` ##### `greaterThan` and `greaterThanOrEqual` number filtering  The `greaterThan` filter key is used to filter down to records where the number field is greater than a given number. ```typescript await api.widget.findMany({ filter: { quantity: { greaterThan: 10, }, }, }); ``` Similarly, the `greaterThanOrEqual` filter key is used to filter down to records where the number field is greater than or equal to a given number. ```typescript await api.widget.findMany({ filter: { quantity: { greaterThanOrEqual: 10, }, }, }); ``` #### Filtering on boolean fields  boolean fields can be filtered to return only records with the boolean set to `true`, `false`, or where the value is set to anything at all. For example, we can check if the `isPublished` boolean field is set to true with `equals: true`: ```typescript await api.posts.findMany({ filter: { isPublished: { equals: true, }, }, }); ``` Or find those that are not published with `equals: false`: ```typescript await api.posts.findMany({ filter: { isPublished: { equals: false, }, }, }); ``` The list of supported filter keys for boolean fields is: * `equals` * `notEquals` * `isSet` ##### `equals` boolean filtering  The `equals` filter key is used to filter down to records where the boolean field is equal to true or false. `equals` will only return records where the stored boolean value matches the provided one. ```typescript await api.widget.findMany({ filter: { isPublished: { equals: true, }, }, }); ``` ##### `notEquals` boolean filtering  The `notEquals` filter key is used to filter down to records where the boolean field is set to anything other than a given boolean. `notEquals` checks if the incoming boolean is exactly equal to the stored boolean, and will only return records where that is false. This can include records where the stored value is set to `null`. ```typescript await api.widget.findMany({ filter: { isPublished: { notEquals: true, }, }, }); ``` ##### `isSet` boolean filtering  The `isSet` filter key is used to filter down to records where the boolean field is set to anything other than `null`. `isSet` will only return records where the value is not `null`. ```typescript await api.widget.findMany({ filter: { isPublished: { isSet: true, }, }, }); ``` `isSet` can also be used to check for null by passing `isSet: false`. ```typescript await api.widget.findMany({ filter: { isPublished: { isSet: false, }, }, }); ``` #### Filtering on date / time fields  The date / time field type supports filtering on date-time fields. Date-time fields can be filtered by null-ness, equality, and by date range. For example, we can filter a post model by the `createdAt` date / time field to get all posts written in the past week: ```typescript const oneWeekAgo = new Date(Date.now() - 604800000); await api.post.findMany({ filter: { createdAt: { after: oneWeekAgo, }, }, }); ``` Date times can be provided to the filter API as `Date` objects or as ISO 8601 date-time strings. ```typescript // all datetime filters support being passed real `Date` objects await api.post.findMany({ filter: { createdAt: { after: new Date("2024-01-01T00:00:00Z"), }, }, }); // and also support being passed ISO 8601 date-time strings await api.post.findMany({ filter: { createdAt: { after: "2024-01-01T00:00:00Z", }, }, }); ``` 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 full list of supported filters on date / time fields are: * * * * * * * * * * * ##### `equals` date-time filtering  The `equals` filter key is used to filter down to records where the date / time field is equal to a given date-time. `equals` does an exact match to check if the incoming date-time is exactly equal to the stored date-time, and will only return records where that is true. ```typescript await api.post.findMany({ filter: { createdAt: { equals: "2023-07-15T10:00:00Z", }, }, }); ``` ##### `notEquals` date-time filtering  The `notEquals` filter key is used to filter down to records where the date / time field is set to anything other than a given date-time. `notEquals` does an exact match to check if the incoming date-time is exactly equal to the stored date-time, and will only return records where that is false. ```typescript await api.post.findMany({ filter: { createdAt: { notEquals: "2023-07-15T10:00:00Z", }, }, }); ``` ##### `isSet` date-time filtering  The `isSet` filter key is used to filter down to records where the date / time field is set to anything other than `null`. `isSet` will only return records where the value is not `null`. ```typescript await api.post.findMany({ filter: { createdAt: { isSet: true, }, }, }); ``` `isSet` can also be used to check for null by passing `isSet: false`. ```typescript await api.post.findMany({ filter: { createdAt: { isSet: false, }, }, }); ``` ##### `in` date-time filtering  The `in` filter key is used to filter down to records where the date / time field is exactly equal to one of the provided date-times. `in` does an exact comparison between each of the provided date-times to the date-time stored in the database, and will return records where the date-time field is equal to any of the provided date-times. The `in` filter will never return records where the stored date-time is null. ```typescript await api.post.findMany({ filter: { createdAt: { in: ["2023-07-15T10:00:00Z", "2023-07-16T12:00:00Z"], }, }, }); ``` ##### `notIn` date-time filtering  The `notIn` filter key is used to filter down to records where the date / time field is not equal to any of the provided date-times. `notIn` does an exact comparison between each of the provided date-times to the date-time stored in the database, and will return records where the date-time field is not equal to any of the provided date-times. `notIn` will also return records where the stored date-time is null. ```typescript await api.post.findMany({ filter: { createdAt: { notIn: ["2023-07-15T10:00:00Z", "2023-07-16T12:00:00Z"], }, }, }); ``` ##### `lessThan` and `lessThanOrEqual` date-time filtering  The `lessThan` date / time key is used to filter down to records where the date / time field is less than a given date-time. ```typescript await api.post.findMany({ filter: { createdAt: { lessThan: "2023-07-15T10:00:00Z", }, }, }); ``` Similarly, the `lessThanOrEqual` filter key is used to filter down to records where the date / time field is less than or equal to a given date-time. ```typescript await api.post.findMany({ filter: { createdAt: { lessThanOrEqual: "2023-07-15T10:00:00Z", }, }, }); ``` ##### `greaterThan` and `greaterThanOrEqual` date-time filtering  The `greaterThan` filter key is used to filter down to records where the date / time field is greater than a given date-time. ```typescript await api.post.findMany({ filter: { createdAt: { greaterThan: "2023-07-15T10:00:00Z", }, }, }); ``` Similarly, the `greaterThanOrEqual` filter key is used to filter down to records where the date / time field is greater than or equal to a given date-time. ```typescript await api.post.findMany({ filter: { createdAt: { greaterThanOrEqual: "2023-07-15T10:00:00Z", }, }, }); ``` ##### `before` date-time filtering  The `before` filter key is an alias for the `lessThan` filter, used to filter down to records where the date / time field is before a given date-time. ```typescript await api.post.findMany({ filter: { createdAt: { before: "2023-07-15T10:00:00Z", }, }, }); ``` ##### `after` date-time filtering  The `after` filter key is an alias for the `greaterThan` filter, used to filter down to records where the date / time field is after a given date-time. ```typescript await api.post.findMany({ filter: { createdAt: { after: "2023-07-15T10:00:00Z", }, }, }); ``` #### Filtering on idfields  The id field type supports filtering on ID fields. IDs can be filtered by null-ness, equality, and by range. For example, we can filter a student model by the `id` field to get all students with specific IDs: ```typescript await api.student.findMany({ filter: { id: { in: ["1", "2", "3"], }, }, }); ``` IDs can be passed as strings or numbers and Forgelivo will ensure the types are converted before comparison. The full list of supported filters on id fields are: * * * * * * * * * ##### `equals` ID filtering  The `equals` filter key is used to filter down to records where the id field is equal to a given ID. `equals` does an exact match to check if the incoming ID is exactly equal to the stored ID, and will only return records where that is true. ```typescript await api.student.findMany({ filter: { id: { equals: "1", }, }, }); ``` ##### `notEquals` ID filtering  The `notEquals` filter key is used to filter down to records where the id field is set to anything other than a given ID. `notEquals` does an exact match to check if the incoming ID is exactly equal to the stored ID, and will only return records where that is false. ```typescript await api.student.findMany({ filter: { id: { notEquals: "1", }, }, }); ``` ##### `isSet` ID filtering  The `isSet` filter key is used to filter down to records where the id field is set to anything other than `null`. `isSet` will only return records where the value is not `null`. ```typescript await api.student.findMany({ filter: { id: { isSet: true, }, }, }); ``` `isSet` can also be used to check for null by passing `isSet: false`. ```typescript await api.student.findMany({ filter: { id: { isSet: false, }, }, }); ``` ##### `in` ID filtering  The `in` filter key is used to filter down to records where the id field is exactly equal to one of the provided IDs. `in` does an exact comparison between each of the provided IDs to the ID stored in the database, and will return records where the ID field is equal to any of the provided IDs. The `in` filter will never return records where the stored ID is null. ```typescript await api.student.findMany({ filter: { id: { in: ["1", "2", "3"], }, }, }); ``` ##### `notIn` ID filtering  The `notIn` filter key is used to filter down to records where the id field is not equal to any of the provided IDs. `notIn` does an exact comparison between each of the provided IDs to the ID stored in the database, and will return records where the ID field is not equal to any of the provided IDs. `notIn` will also return records where the stored ID is null. ```typescript await api.student.findMany({ filter: { id: { notIn: ["1", "2", "3"], }, }, }); ``` ##### `lessThan` and `lessThanOrEqual` ID filtering  The `lessThan` id key is used to filter down to records where the id field is less than a given ID. ```typescript await api.student.findMany({ filter: { id: { lessThan: "10", }, }, }); ``` Similarly, the `lessThanOrEqual` filter key is used to filter down to records where the id field is less than or equal to a given ID. ```typescript await api.student.findMany({ filter: { id: { lessThanOrEqual: "10", }, }, }); ``` ##### `greaterThan` and `greaterThanOrEqual` ID filtering  The `greaterThan` filter key is used to filter down to records where the id field is greater than a given ID. ```typescript await api.student.findMany({ filter: { id: { greaterThan: "10", }, }, }); ``` Similarly, the `greaterThanOrEqual` filter key is used to filter down to records where the id field is greater than or equal to a given ID. ```typescript await api.student.findMany({ filter: { id: { greaterThanOrEqual: "10", }, }, }); ``` #### Filtering on enum Fields  The enum field type supports filtering on enum fields, which can store predefined string values. Enum fields can be filtered by null-ness, equality, inequality, and containment of values when multiple selections are allowed. The following filters are supported on enum fields: * * * * * ##### `isSet` enum filtering  The `isSet` filter key is used to filter down to records where the enum field is set to anything other than `null`. `isSet` will only return records where the value is not `null`. ```typescript await api.tickets.findMany({ filter: { status: { isSet: true, }, }, }); ``` `isSet` can also be used to check for null by passing `isSet: false`. ```typescript await api.tickets.findMany({ filter: { status: { isSet: false, }, }, }); ``` ##### `equals` enum filtering  The `equals` filter key is used to filter down to records where the enum field is equal to a given value. `equals` does an exact match to check if the incoming value is exactly equal to the stored value and will only return records where that is true. ```typescript await api.tickets.findMany({ filter: { status: { equals: "in-progress", }, }, }); ``` For enum fields where the `allowMultiple` option is true, the stored value is an array of strings. `equals` must be passed an array of strings for these fields, and will only return records where the stored value is an array containing exactly the provided values. `equals` on enum fields where the `allowMultiple` option is true is case sensitive and order sensitive. It will only return records where the stored value is an array containing exactly the provided values in the provided order. If you need to check partial matches, use the filter. ```typescript await api.client.findMany({ filter: { communication: { equals: ["SMS", "Email"], }, }, }); ``` This filter would match a record like `{ communication: ["SMS", "Email"] }`. This filter would not match records like: * `{ communication: ["SMS"] }` because there's an element missing * `{ communication: ["Email", "SMS"] }` because the order is wrong * `{ communication: ["sms", "email"] }` because the casing doesn't match * `{ communication: ["SMS", "Email", "In-Person"] }` because there is an extra element ##### `notEquals` enum filtering  The `notEquals` filter key is used to filter down to records where the enum field is set to anything other than a given value. `notEquals` does an exact match to check if the incoming value is exactly equal to the stored value and will only return records where that is false. ```typescript await api.tickets.findMany({ filter: { status: { notEquals: "backlog", }, }, }); ``` For enum fields where the `allowMultiple` option is true, the stored value is an array of strings. `notEquals` must be passed an array of strings for these fields, and will only return records where the stored value does not exactly equal the provided values. ```typescript await api.client.findMany({ filter: { communication: { notEquals: ["SMS", "Email"], }, }, }); ``` This filter would not match a record like `{ communication: ["SMS", "Email"] }`. This filter will match records like: * `{ communication: null }` because null isn't equal to the provided value * `{ communication: ["SMS"] }` because there's an element missing * `{ communication: ["Email", "SMS"] }` because the order is wrong * `{ communication: ["sms", "email"] }` because the casing doesn't match * `{ communication: ["SMS", "Email", "In-Person"] }` because there is an extra element ##### `in` enum filtering  The `in` filter key is used to filter down to records where the enum field is exactly equal to one of the provided values. `in` does an exact comparison between each of the provided values to the value stored in the database and will return records where the enum field is equal to any of the provided values. The `in` filter will never return records where the stored value is null. ```typescript await api.tickets.findMany({ filter: { status: { in: ["backlog", "in-progress"], }, }, }); ``` ##### `contains` enum filtering  When the `allowMultiple` field configuration is turned on for a enum field, the `contains` filter is available for checking if the stored record value contains one or more provided values. `contains` checks if all provided filter values are present but does not perform an exact match, so there can be more stored values. ```typescript await api.client.findMany({ filter: { communication: { contains: ["SMS", "Email"], }, }, }); ``` This filter would match records like: * `{ communication: ["SMS", "Email"] }` * `{ communication: ["Email", "SMS"] }` * `{ communication: ["SMS", "Email", "In-Person"] }` This filter would not match records like: * `{ communication: ["SMS"] }` because there's an element missing * `{ communication: ["sms", "email"] }` because the casing doesn't match 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 additional options selected beyond "Sms" and "Email". #### Filtering on json Fields  The json field type supports filtering on JSON fields. JSON fields can be filtered by their structure, values, and presence. For example, we can filter a system setup model by the `configuration` json field to get all setups where the JSON contains a specific key-value pair: ```typescript await api.systemSetup.findMany({ filter: { configuration: { matches: { foo: "bar" }, }, }, }); ``` We can also filter on JSON arrays stored in json fields, for example, filtering by the `tags` field on a product model: ```typescript await api.product.findMany({ filter: { tags: { matches: "Accessory", }, }, }); ``` The full list of supported filters on json fields are: * * * * * * When filtering on json fields, you will almost always want to use a . ##### `isSet` JSON filtering  The `isSet` filter key is used to filter down to records where the json field is set to anything other than `null`. `isSet` will only return records where the value is not `null`. ```typescript await api.systemSetup.findMany({ filter: { configuration: { isSet: true, }, }, }); ``` `isSet` can also be used to check for null by passing `isSet: false`. ```typescript await api.systemSetup.findMany({ filter: { configuration: { isSet: false, }, }, }); ``` ##### `equals` JSON filtering  The `equals` filter key is used to filter down to records where the json field is exactly equal to a given JSON value. `equals` does an exact match to check if the incoming JSON is exactly equal to the stored JSON, and will only return records where that is true. ```typescript await api.systemSetup.findMany({ filter: { configuration: { equals: { foo: "bar" }, }, }, }); ``` ##### `notEquals` JSON filtering  The `notEquals` filter key is used to filter down to records where the json field is set to anything other than a given JSON value. `notEquals` does an exact match to check if the incoming JSON is exactly equal to the stored JSON, and will only return records where that is false. ```typescript await api.systemSetup.findMany({ filter: { configuration: { notEquals: { foo: "bar" }, }, }, }); ``` ##### `in` JSON filtering  The `in` filter key is used to filter down to records where the json field is exactly equal to one of the provided JSON values. `in` does an exact comparison between each of the provided JSON values to the JSON stored in the database, and will return records where the JSON field is equal to any of the provided values. The `in` filter will never return records where the stored JSON is null. ```typescript await api.systemSetup.findMany({ filter: { configuration: { in: [{ foo: "bar" }, { fizz: "buzz" }], }, }, }); ``` This filter would match records like: * `{ configuration: { foo: "bar" } }` * `{ configuration: { fizz: "buzz" } }` This filter would not match records like: * `{ configuration: { foo: "something else" } }` because the value of `foo` is not `bar` * `{ configuration: { foo: "bar", fizz: "buzz" } }` because the overall value is not equal to either provided value * `{ configuration: { foo: { bar: "baz" } } }` because the value of `foo` is not a string * `{ configuration: null }` because the whole value is missing ##### `notIn` JSON filtering  The `notIn` filter key is used to filter down to records where the json field is not equal to any of the provided JSON values. `notIn` does an exact comparison between each of the provided JSON values to the JSON stored in the database, and will return records where the JSON field is not equal to any of the provided values. `notIn` will also return records where the stored JSON is null. ```typescript await api.systemSetup.findMany({ filter: { configuration: { notIn: [{ foo: "bar" }, { fizz: "buzz" }], }, }, }); ``` ##### `matches` JSON filtering  The `matches` filter key filters down to records where the json field contains the provided JSON structure as a subset. The `matches` filter uses the `@>` operator under the hood in Postgres. ```typescript await api.systemSetup.findMany({ filter: { configuration: { matches: { foo: "bar" }, }, }, }); ``` This filter would match records like: * `{ configuration: { foo: "bar" } }` * `{ configuration: { foo: "bar", fizz: "buzz" } }` This filter would not match records like: * `{ configuration: { foo: "buzz" } }` because the value of `foo` is not `bar` * `{ configuration: { foo: { bar: "buzz" } } }` because the value of `foo` is not a string * `{ configuration: { one: "two" } }` because the key `foo` is missing * `{ configuration: null }` because the whole value is missing The `matches` filter can be used to match nested structures as well: ```typescript await api.systemSetup.findMany({ filter: { configuration: { matches: { foo: { bar: "baz" } }, }, }, }); ``` This will return records where the configuration field contains a nested structure matching `{ foo: { bar: "baz" } }`, allowing extra keys at either level. The `matches` filter can also be used to match nested arrays within the JSON structure. This allows you to filter records where a JSON field contains an array with specific elements. For example, to get a page of system setup records that have the configuration containing a `foo` array with the element `"bar"`: ```typescript await api.systemSetup.findMany({ filter: { configuration: { matches: { foo: ["bar"] }, }, }, }); ``` This filter will match records where the `configuration` field contains an array with the element `"bar"` in the `foo` array. The array can contain additional elements, but it must include `"bar"` as one of its elements. For example, this filter would match records like: * `{ configuration: { foo: ["bar"] } }` * `{ configuration: { foo: ["bar", "baz"] } }` * `{ configuration: { foo: ["baz", "bar"] } }` This filter would not match records like: * `{ configuration: { foo: ["baz"] } }` because the array does not contain `"bar"` * `{ configuration: { foo: [] } }` because the array is empty * `{ configuration: { foo: null } }` because the array is missing * `{ configuration: { foo: "bar" } }` because the value is not an array #### Filtering on vector fields  vector fields store an array of floats, called [vectors](https://docs.forgelivo.com/guides/plugins/openai/building-ai-apps#storing-vectors-on-models), in the database. Vector filters can be used to only return records that have a vector field similar to an input vector. Most often, vector filters are paired with vector sorts to find vectors that are most similar to an input vector. Read more about vector sorts . Vector filters allow you to cap the maximum vector distance in a vector similarity search. ##### Filtering on vector similarity  Vector similarity features allow you to filter records down to only those that have a stored vector that is similar to an input vector. Vector similarity allows you to implement [semantic search](https://docs.forgelivo.com/guides/glossary#semantic-search). To filter based on similarity, you must provide an input vector to compare all the stored vectors against, and a similarity threshold to filter using. When you query, each stored vector is compared to the provided vector using the operator you've selected, and only those passing the threshold are returned. Vector similarity searches support two measures of similarity: and . For example, we can filter the `document` model to return only records with a vector that is similar to the input vector `[1, 2, 3]` in the `embedding` field: ```typescript const records = await api.document.findMany({ filter: { embedding: { cosineSimilarity: { to: [1, 2, 3], greaterThan: 0.8, }, }, }, }); ``` This will return records from the `document` model that have a vector stored in the `embedding` field that is similar to the input vector `[1, 2, 3]` as measured by cosine similarity. 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. ##### `cosineSimilarity` vector filtering  `cosineSimilarity` filters compute the cosine similarity between each stored vector and the input vector and then return records where the cosine similarity passes the provided threshold. The threshold can be a `greaterThan` threshold, which will return only records that are more similar than the threshold in the result, or a `lessThan` threshold, which will return vectors that are less similar than the threshold. For more on the math behind cosine similarity and other vector distance measures, see [this great post](https://tuhinmukherjee74.medium.com/different-types-of-distances-used-in-machine-learning-explained-550e2979752c). 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 with an input vector: ```typescript const records = await api.document.findMany({ filter: { embedding: { cosineSimilarity: { to: [1, 2, 3], greaterThan: 0.8, }, }, }, }); ``` Usually, this similarity filter would be paired with a similarity sort to return the most similar vectors first: ```typescript const records = await api.document.findMany({ sort: { embedding: { cosineSimilarityTo: [1, 2, 3], }, }, filter: { embedding: { cosineSimilarity: { to: [1, 2, 3], greaterThan: 0.8, }, }, }, }); ``` The threshold for vector filtering can be one of `lessThan`, `lessThanOrEqual`, `greaterThan`, or `greaterThanOrEqual`. `greaterThan` means that the vectors must be at least this similar or more so (close), and `lessThan` means that the vectors must be at most this similar or less so (far). For example, you can filter to find the records that have at most a similarity of 0.1 from a stored set: ```typescript const records = await api.document.findMany({ filter: { embedding: { cosineSimilarity: { to: [1, 2, 3], lessThan: 0.1, }, }, }, }); ``` ##### `l2Distance` vector filtering  vector fields can be filtered based on their L2 (Euclidean) distance to an input vector. The `l2Distance` filter computes the l2 distance between each stored vector and the input vector and then only returns records where the distance passes the provided threshold. The threshold can be a `greaterThan` threshold, which will return only records that are more similar than the threshold in the result, or a `lessThan` threshold, which will return vectors that are less similar than the threshold. For more on the math behind L2 distance and other vector distance measures, see [this great post](https://tuhinmukherjee74.medium.com/different-types-of-distances-used-in-machine-learning-explained-550e2979752c). 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: ```typescript await api.someModel.findMany({ filter: { embedding: { l2Distance: { to: [1, 0, 1], lessThanOrEqualTo: 1, }, }, }, }); ``` The threshold for vector filtering can be one of `lessThan`, `lessThanOrEqual`, `greaterThan`, or `greaterThanOrEqual`. `greaterThan` means that the vectors must be at least this similar or more so (close), and `lessThan` means that the vectors must be at most this similar or less so (far). For example, you can filter to find the records that have at most a similarity of 0.1 from a stored set: ```typescript const records = await api.document.findMany({ filter: { embedding: { l2Distance: { to: [1, 2, 3], lessThan: 0.1, }, }, }, }); ``` ##### `equals` vector filtering  You can filter vector fields to only return records where a record's stored vector is exactly equal to a provided vector using `equals`. `equals` does an exact comparison, which means every element of the vector must be identical between the stored value and the provided value for the record to pass the filter. Vector equality does _not_ compute similarity or distance. For example, to filter to only records that have a vector set to `[1,2,3]`, you can use the `equals` filter: ```typescript await api.document.findMany({ filter: { embedding: { equals: [1, 2, 3], }, }, }); ``` ##### `isSet` vector filtering  Vector fields can be filtered down to only the records that have a vector set at all with `isSet: true`, or only those records without a vector set with `isSet: false`. For example, to filter to only records that have a vector field named `embedding` set, you can use the `isSet: true` filter: ```typescript await api.document.findMany({ filter: { embedding: { isSet: true, }, }, }); ``` You could also filter to only records without a vector set with `isSet: false`: ```typescript await api.document.findMany({ filter: { embedding: { isSet: false, }, }, }); ``` #### Filtering on record state fields  record state fields can be filtered as if they are strings with a small number of exceptions. Record state values in Forgelivo are usually nested JSON objects, not just simple strings When filtering on state values, ensure you use the fully qualified state value, like `created.installed`. When passing state values to filters, you can pass them as strings or objects. For example, you can filter to find a record that looks like this: ```json { "id": "123", "state": { "created": "installed" } } ``` using the string form of a state value: ```typescript await api.shopifyShop.findMany({ filter: { state: { equals: "created.installed" }, }, }); ``` or using the object form of a state value: ```typescript await api.shopifyShop.findMany({ filter: { state: { equals: { created: "installed" } }, }, }); ``` The string form and object form are equivalent, and will return the same records. Neither form supports abbreviated state values, so you must use the fully qualified state value, like `created.installed`. ##### `equals` state filters  The `equals` filter returns only records that are currently in a specific state for their record state field. ```typescript await api.shopifyShop.findMany({ filter: { state: { equals: "created.installed" }, }, }); ``` State values can also be passed in object form, like: ```typescript await api.shopifyShop.findMany({ filter: { state: { equals: { created: "installed" } }, }, }); ``` ##### `notEquals` state filtering  The `notEquals` filter key is used to filter down to records where the record state field is set to anything other than a given state. `notEquals` does a case-sensitive match to check if the incoming state is exactly equal to the stored state, and will only return records where that is false. ```typescript await api.widget.findMany({ filter: { name: { notEquals: "created.draft", }, }, }); ``` ##### `isSet` state filters  The `isSet` filter allows checking for records where the state field is set. For example, we can filter to only those shops that have a state currently stored in the database: ```typescript await api.shopifyShop.findMany({ filter: { state: { isSet: true }, }, }); ``` Or filter to records that currently have a `null` state: ```typescript await api.shopifyShop.findMany({ filter: { state: { isSet: false }, }, }); ``` ##### `in` state filtering  The `in` filter key is used to filter down to records where the record state field is exactly equal to one of the provided states. `in` does a case-sensitive exact comparison between each of the provided states to the state stored in the database, and will return records where the state field is equal to any of the provided states. The `in` filter will never return records where the stored state is null. ```typescript await api.shopifyShop.findMany({ filter: { state: { in: ["created.installed", "created.uninstalled"], }, }, }); ``` ##### `notIn` state filtering  The `notIn` filter key is used to filter down to records where the record state field is not equal to any of the provided states. `notIn` does a case-sensitive exact comparison between each of the provided states to the state stored in the database, and will return records where the state field is not equal to any of the provided states. `notIn` will also return records where the stored state is null. ```typescript await api.shopifyShop.findMany({ filter: { state: { notIn: ["created.uninstalled", "created.fraudulent"], }, }, }); ``` ##### `inState` state filtering  The `inState` filter key is a deprecated alias for the `equals` filter key on the record state field. In Forgelivo framework version 1.0.0 or later, there's no difference between `inState` and `equals`, and `equals` should be preferred. #### Filtering on role list fields  The role list field type supports filtering on role lists, which can store access control roles predefined for your Forgelivo app. Role list fields can be filtered by null-ness, equality, inequality, and containment of values when multiple selections are allowed. The following filters are supported on role list fields: * * * * You will often want to use when filtering role list fields. ##### `isSet` role list filtering  The `isSet` filter key is used to filter down to records where the role list field is set to anything other than `null`. `isSet` will only return records where the value is not `null`. ```typescript await api.user.findMany({ filter: { roles: { isSet: true, }, }, }); ``` `isSet` can also be used to check for null by passing `isSet: false`. ```typescript await api.user.findMany({ filter: { roles: { isSet: false, }, }, }); ``` ##### `equals` role list filtering  The `equals` filter key is used to filter down to records where the role list field is equal to a given value. `equals` does an exact match to check if the incoming value is exactly equal to the stored value and will only return records where that is true. ```typescript await api.user.findMany({ filter: { roles: { equals: ["signed-in"], }, }, }); ``` role list fields store an array of strings. `equals` must be passed an array of strings for these fields, and will only return records where the stored value is an array containing exactly the provided values. ```typescript await api.user.findMany({ filter: { roles: { equals: ["signed-in", "admin"], }, }, }); ``` This filter would match a record like `{ roles: ["signed-in", "admin"] }`. This filter would not match records like: * `{ roles: ["signed-in"] }` because there's an element missing * `{ roles: ["admin", "signed-in"] }` because the order is wrong * `{ roles: ["Signed-In", "Admin"] }` because the casing doesn't match * `{ roles: ["signed-in", "admin", "superuser"] }` because there is an extra element ##### `notEquals` role list filtering  The `notEquals` filter key is used to filter down to records where the role list field is set to anything other than a given value. `notEquals` does an exact match to check if the incoming value is exactly equal to the stored value and will only return records where that is false. ```typescript await api.user.findMany({ filter: { roles: { notEquals: ["unauthenticated"], }, }, }); ``` role list fields store an array of strings. `notEquals` must be passed an array of strings for these fields, and will only return records where the stored value does not exactly equal the provided values. ```typescript await api.user.findMany({ filter: { roles: { notEquals: ["signed-in", "admin"], }, }, }); ``` This filter would not match a record like `{ roles: ["signed-in", "admin"] }`. This filter will match records like: * `{ roles: null }` because null isn't equal to the provided value * `{ roles: ["signed-in"] }` because there's an element missing * `{ roles: ["admin", "signed-in"] }` because the order is wrong * `{ roles: ["Signed-In", "Admin"] }` because the casing doesn't match * `{ roles: ["signed-in", "admin", "superuser"] }` because there is an extra element ##### `contains` role list filtering  The `contains` filter is available for checking if stored role list record values contains one or more provided values. `contains` checks if all provided filter values are present but does not perform an exact match, so there can be more stored values. ```typescript await api.client.findMany({ filter: { roles: { contains: ["signed-in", "admin"], }, }, }); ``` This filter would match records like: * `{ roles: ["signed-in", "admin"] }` * `{ roles: ["admin", "signed-in"] }` * `{ roles: ["signed-in", "admin", "superuser"] }` This filter would not match records like: * `{ roles: ["signed-in"] }` because there's an element missing * `{ roles: ["Signed-In", "Admin"] }` because the casing doesn't match This will return all `user` records that have both the "signed-in" and "admin" options selected. Because this is not an exact match, this will also include `user` records that have additional roles beyond "signed-in" and "admin". ### 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: ```typescript await api.post.findMany({ filter: { publishDate: { isSet: true }, }, }); ``` Or filter the Shopify Order model to find records where the `customMetafield` field is currently `null`: ```typescript await api.shopifyOrder.findMany({ filter: { customMetafield: { isSet: false }, }, }); ``` ### Filtering models by a related model's properties [Framework v1.3.0+](https://docs.forgelivo.com/guides/gadget-framework)  You can filter a query on a model by the properties of a related model by traversing the relationship. This is applicable to belongs to, has one, has many, and has many through relationships. These relationship traversals in a filter can be chained up to a maximum of 2 steps, where each individual chain can only contain a single has many or has many through relationship. Note: is available in all Forgelivo framework versions. Filtering on related models is not supported on queries that also make use of the `search` API. #### Filtering on belongs to ID fields  You can filter on the relationship id field of the related model for belongs to relationships. 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 `post`s by `author` with an ID of 1, you could: ```typescript const posts = await api.post.findMany({ filter: { authorId: { equals: 1, }, }, }); ``` `isSet` can also be used on belongs to relationship fields directly to check if a relationship exists for a record. ```typescript const posts = await api.post.findMany({ filter: { author: { isSet: true, }, }, }); ``` #### Filtering across belongs to and has one relationships  You can filter on the single related record in a belongs to and has one relationship. For example, if you have a blog application, you might have `post` and `comment` models, were `post` has many `comment` and `comment` belongs to `post`. You might want to query for comments on all blog posts who have a title starting with the letter X: ```typescript const commentsForPostsStartingWithX = await api.comment.findMany({ filter: { post: { title: { startsWith: "X", }, }, }, }); ``` The relationship field on the queried model, `post`, is used to filter on the related model's `title` field. You can also filter by relationships on the related model. For example, if `post` has a related `author` you can fetch all comments for Carl Weathers's blog posts: ```typescript const commentsForCarlPosts = await api.comment.findMany({ filter: { post: { author: { name: { equals: "Carl Weathers", }, }, }, }, }); ``` Attempting to filter by a related field on `author` will result in an error because the maximum number of related field filter steps (2) has been reached. #### Filtering on has many and has many through relationships  Filter conditions on has many and has many through require the use of `some:` or `every:` to determine whether or not the filter condition across the relationship needs to be true for at least one related record or every related record, and they must _immediately_ follow the relationship field name in a `filter` statement. The format looks like the following: ```typescript await api.order.findMany({ filter: { // name of the relationship field on order model lineItems: { // use some: or every: immediately after the has many (through) relationship every: { // field on the related model price: { // filter condition greaterThan: 20, }, }, }, }, }); ``` If only **some** of the related objects should satisfy the filter, use `some:`. For example, if you have two related models where each `teacher` has many `student` and you want to query for teachers that have some students with grades greater than or equal to 50: ```js // in Filter for teachers with some students that have a grade over 50 const teachersWithAStudentPassing = await api.teacher.findMany({ filter: { students: { // use some: on the relationship some: { grade: { greaterThanOrEqual: 50, }, }, }, }, }); ``` If a filter condition needs to be true on **every** related object, the `every:` field can be used. For example, if you want to get teachers whose students all have grades over 50, you could do the following: ```typescript const teachersWithOnlyPassingStudents = await api.teacher.findMany({ filter: { students: { // use every: on the relationship every: { grade: { greaterThanOrEqual: 50, }, }, }, }, }); ``` The `some` and `every` fields can also be used when filtering across multiple relationships, for example, fetching blog posts that have some comments authored by Bill: ```typescript const postsWithCommentsByBill = await api.post.findMany({ filter: { comments: { // use some: and then filter by another relationship some: { author: { name: { equals: "Bill", }, }, }, }, }, }); ``` When there are no related records `some:` will return `false` and `every:` will return `true`. #### Filtering by related models in GraphQL  You can also filter related models by the related model's attributes using GraphQL. For example, you could filter for `author` records who live in Canada and include their blog `posts` that are longer than 500 words: ```graphql query { authors(filter: { country: { equals: "Canada" } }) { edges { node { id name country posts(filter: { wordCount: { greaterThan: 500 } }) { edges { node { id title wordCount } } } } } } } ``` This would return all authors along with their blog posts that have a word count greater than 500. ### Unfilterable fields  Currently, Forgelivo doesn't support filtering for the following field types: * file * encrypted string * password * computed ## Searching  Forgelivo search can be used to power autocompletes, searchable tables, or other experiences where humans are writing search queries. The `search` API is available on your `findMany`, `maybeFindMany`, `findFirst`, and `maybeFindFirst` read APIs on all models that have search indexing enabled: ```typescript const records = await api.myModel.findMany({ search: "a specific phrase to search for", }); ``` By default, search isn't field specific in Forgelivo. All of the following field types are searched with the built in search functionality: * boolean * belongs to (searches on the related record ID) * color * date / time * email * enum * id * json * money * number * record state * rich text * role list * string * url In order to search on specific fields, you can pass the `searchFields` option to any read API call: ```typescript const records = await api.post.findMany({ searchFields: { title: true, content: { weight: 2.5 }, // optionally, you can specify a weight for the field }, }); ``` The `search` API is not supported on queries that also . ### Customizing `search`  `search` is powered by ElasticSearch, which allows you to leverage advanced search capabilities like fuzzy matching and phrase matching, to power a typo tolerant, synonym aware search experience. This means you can make use of [ElasticSearch's simple query string syntax](https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-simple-query-string-query#simple-query-string-query-notes) in your queries. By default, `search` will append a `*` operator to the end of your query which will perform a _prefix search_. This means that searching for `foo` will match records with `foo`, `foobar`, and `foobarbaz`. ```typescript const records = await api.myModel.findMany({ // ElasticSearch query is actually 'foo*' search: "foo", }); ``` When you add your own custom ElasticSearch operator, `search` will use that operator instead of the default prefix search. This means you can modify search queries to fit your specific needs. For example, you could search for `blogPost` records that contain either `software` or `development` using ElasticSearch's or operator (`|`): ```typescript const records = await api.blogPost.findMany({ search: "software | development", }); ``` Or you could search for `blogPost` records that contain the exact phrase `zero downtime` using the `"` operator: ```typescript const records = await api.blogPost.findMany({ search: '"zero downtime"', }); ``` ### Specifying fields to search with `searchFields`  You can specify exactly which fields to search and assign relative weights to control how results are ranked. This gives you fine-grained control over search behavior at the API call level. By default, `search` will search across _all_ indexed fields on your model. With `searchFields`, you can limit the search to specific fields or boost the importance of certain fields in the search results. ```typescript await api.post.findMany({ search: "gadget", searchFields: { title: true, content: true, // other indexed fields won't be searched }, }); ``` The `searchFields` parameter is available on `findMany`, `findFirst`, `maybeFindMany`, and `maybeFindFirst` for models with search indexing enabled. Requires framework version 1.5.0+. #### Boosting field importance with weights  You can assign relative weights to fields to control how results are ranked. Fields with higher weights will have their matches ranked higher in the search results: ```typescript await api.post.findMany({ search: "gadget", searchFields: { // titles matching "gadget" ranked higher title: { weight: 2.0 }, // standard weight for content content: { weight: 1.0 }, // tags are less important tags: { weight: 0.5 }, }, }); ``` When using weights, fields with higher weights will have their matches contribute more to the overall relevance score. For example, a match in a field with `weight: 2.0` will be ranked twice as highly as a match in a field with `weight: 1.0`.