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.