Fetching data
Queries

Queries

Defining queries

Data in Triplit is organized into collections, so to define a query you must first specify which collection you want to query.

For example, if you want to query the users collection, you would write the following:

const query = client.query('users');

Selecting attributes

To specify which attributes you want to return, you can use the select method. This method accepts a list of attribute names for the collection as arguments.

const query = client.query('users').select(['id', 'name', 'email', 'dob']);

If the type you are selecting is a record, you may also select a specific attribute of the record by using dot notation. The result will be an object with just the selected keys.

const query = client
  .query('users')
  .select(['id', 'address.street', 'address.city']);
// {id: 'abc', address: {street: '123 Main St', city: 'New York'}}

If you do not call select on a query, all attributes are selected.

Selecting related entities

If you have defined a relation in your schema using RelationById, RelationOne, or RelationMany, you can choose to select entities defined by the relation in a query.

For example, the following schema defines a relation between users and messages

const schema = {
  users: S.Schema({
    id: S.Id(),
    name: S.Id(),
    email: S.String(),
  }),
  messages: S.Schema({
    id: S.Id(),
    text: S.String(),
    sender_id: S.String(),
    sender: S.RelationById('users', '$sender_id'),
  }),
};

By default, a query on messages will not include the sender as an attribute. To include the sender, use the include method in the query builder.

const query = client.query('messages').include('sender');
 
/*
{
  id: '1',
  text: 'hello world!',
  sender_id: 'bob',
  sender: { id: 'bob', name: 'Bob Jones', email: 'bob@triplit.com' },
};
*/

Filtering

To filter results based on conditions, you can use the where method. This method accepts a list of clauses as arguments. A clause is a tuple that takes the form [attribute, operator, value].

For example the following query will return all registered users.

const query = client
  .query('users')
  .select(['id', 'name', 'email', 'dob'])
  .where('is_registered', '=', true);

Clauses can be passed to where as a single clause or an array of clauses:

  • .where('is_registered', '=', true)
  • .where(['is_registered', '=', true])
  • .where([['is_registered', '=', true]])

If multiple clauses are provided, all clauses are joined with a logical AND. However, you may use or and and methods within the clause array to specify how clauses should be logically grouped and joined.

For example the following query will return all registered users who are either an admin or an owner.

import { or } from '@triplit/client';
 
const query = client
  .query('users')
  .select(['id', 'name', 'email', 'dob'])
  .where([
    [
      ['is_registered', '=', true],
      or([
        ['role', '=', 'admin'],
        ['role', '=', 'owner'],
      ]),
    ],
  ]);

You may use dot notation to filter by attributes of a record.

const query = client.query('users').where('address.city', '=', 'New York');

Operators

See the list of data types for more information on the operators that can be used in a where clause.

Order

To order the results of a query, you can use the order method. This method accepts a list of order clauses as an argument. An order clause is a tuple that takes the form [attribute, direction]. direction can be either ASC or DESC. Clauses are applied in the order they are provided.

For example the following query will return all users ordered by their creation date in descending order.

const query = client
  .query('users')
  .select(['id', 'name', 'email', 'dob'])
  .order('created_at', 'DESC');

Clauses can be passed to order as a single clause or an array of clauses:

  • .order('created_at', 'DESC')
  • .order(['created_at', 'DESC'])
  • .order([['created_at', 'DESC']])

You may use dot notation to order by attributes of a record.

const query = client.query('users').order('address.city', 'ASC');

Ordering with relations

If you are using a schema, you can order by attributes of related entities. For example, the following schema defines a relation between users and messages

const schema = {
  users: S.Schema({
    id: S.Id(),
    name: S.String(),
    email: S.String(),
  }),
  messages: S.Schema({
    id: S.Id(),
    text: S.String(),
    created_at: S.Date({ default: S.Default.now() }),
    sender_id: S.String(),
    sender: S.RelationById('users', '$sender_id'),
  }),
};

You can then order messages by the name of the sender.

// Order messages by the name of the sender in ascending order
client.query('messages').order('sender.name', 'ASC');
 
// Order messages by the name of the sender and then by the created date in descending order
client.query('messages').order([
  ['sender.name', 'ASC'],
  ['created_at', 'DESC'],
]);

Ordering with relations is only supported for one-to-one relations, such as RelationById or RelationOne.

After

You may use the after method to specify an entity to start the query from. This is useful for paginating results. You must use order before using after. At the moment, after only supports a single cursor that corresponds to the first order clause.

const PAGE_SIZE = 10;
 
const query = client
  .query('users')
  .select(['id', 'name', 'email', 'dob'])
  .order('created_at', 'DESC')
  .limit(PAGE_SIZE);
 
const { results: firstPage } = client.fetch(query);
const lastEntity = Array.from(firstPage.values()).pop();
 
const secondPageQuery = query.after(lastEntity);
const { results: secondPage } = client.fetch(secondPageQuery);

Limit

Many times you will want to limit the number of results returned by a query. To do this, you can use the limit method.

For example, the following query will return the 10 most recently created users.

const query = client
  .query('users')
  .select(['id', 'name', 'email', 'dob'])
  .order('created_at', 'DESC')
  .limit(10);

As a convenience, Triplit also provides a method fetchOne to fetch just the first entity of a query result.

Variables

Variables in Triplit allow you to pass in preset values into queries. They consist of a scope (to prevent collisions) and a dot (.) separated path to reference data in the variable.

Global variables

Global variables are prefixed with the global scope and are accessible to all queries in the database. They are defined in the DB constructor or via the updateGlobalVariables method. For example:

const db = new DB({ variables: { name: 'Philip J. Fry' } });
const query = db.query('employees').where('name', '=', '$global.name'); // resolves to 'Philip J. Fry'
 
db.updateGlobalVariables({ name: 'Turanga Leela' });
const query = db.query('employees').where('name', '=', '$global.name'); // resolves to 'Turanga Leela'

Session variables

Session variables are prefixed with the session scope and are accessible to all queries in that database session. They are defined with the withSessionVars method. For example:

const db = new DB();
const sessionDB = db.withSessionVars({ name: 'Philip J. Fry' });
const query = db.query('employees').where('name', '=', '$session.name'); // resolves to 'Philip J. Fry'

Query variables

Query variables are prefixed with the query scope and are accessible just to the query they are defined on. They are defined with the variables method in the query builder. For example:

const baseQuery = client.query('employees').where([
  ['team', '=', 'Delivery Crew'],
  ['name', '=', '$query.name'],
]);
const fryQuery = baseQuery.variables({ name: 'Philip J. Fry' });
const leelaQuery = baseQuery.variables({ name: 'Turanga Leela' });

This can help prevent writing the same query multiple times with different values.

Expected variables

Certain variables are used internally by Triplit to aid with authorization and other features. These include:

  • $session.SESSION_USER_ID

Sync state

Triplit's client storage is split into two areas - an outbox for unsynced updates and a cache for synced updates. Sometimes you may want to indicate that to a user that data has not yet been saved to the server. To do this, you can use the syncStatus method. This method accepts a single sync state (pending, confirmed, all) as an argument.

For example, a messaging app could use two queries to build message sent indicators. In React:

const allMessagesQuery = client.query('messages').order(['createdAt', 'ASC']);
 
const pendingMessagesQuery = client
  .query('messages')
  .select(['id'])
  .syncStatus('pending');
 
function Messages() {
  const { results: allMessages } = useQuery(client, allMessagesQuery);
  const { results: pendingMessages } = useQuery(client, pendingMessagesQuery);
 
  return (
    <div>
      {allMessages.entries().map(([id, message]) => (
        <div>
          <p>{message.text}</p>
          <p>{pendingMessages.has(id) ? 'Sending...' : 'Sent'}</p>
        </div>
      ))}
    </div>
  );
}

Entity id

Setting entityId on your query will return exclusively the requested entity if it exists.

For example, the following query will return just the user with id abc, if it exists.

const query = client.query('users').entityId('abc');

You may also pass a DB variable to entityId.

const query = client.query('users').entityId('$userId');

As a convenience, Triplit also provides a method fetchById to fetch an entity by its id.