Queries

About 2 min

Queries

The simplest way to read the items in a space is to use the space.db.query() method. It's also possible to obtain strongly typed results as described below.

Untyped Queries

Once access is obtained to a space, objects can be retrieved:

import { Client } from '@dxos/client';

const client = new Client();

async () => {
  await client.initialize();
  // get a list of all spaces
  const spaces = client.spaces.get();
  // grab a space
  const space = spaces[0];
  // get all items
  const allObjects = space.db.query();
  // get items that match a filter
  const tasks = space.db.query({ type: 'task' });
  // get items that match a predicate
  const finishedTasks = space.db.query(
    (doc) => doc.type == 'task' && doc.isCompleted
  );
};











 

 

 



The result is an iterable collection of objects that can be used like an array.

Typed Queries

It's possible to receive strongly typed results from a query. Pass a type argument to query<T> which descends from TypedObject:

import { Client, TypedObject } from '@dxos/client';

const client = new Client();

class Task extends TypedObject {
  public declare type: 'task';
  public declare isCompleted: boolean;
}

async () => {
  await client.initialize();
  // get a list of all spaces
  const spaces = client.spaces.get();
  // grab a space
  const space = spaces[0];
  // get items that match a filter
  // TODO(wittjosiah): Fix type inference.
  // @ts-ignore
  const tasks = space.db.query<Task>({ type: 'task' });
};




 











 



The type of the resulting objects must descend from TypedObject because ECHO tracks objects returned from query. Mutating the objects directly (setting values, modifying arrays, ..., etc.) will cause ECHO to propagate those changes to all listening ECHO peers in the space.

DXOS provides a tool for conveniently generating entity classes (like Task above) that work with the query<T> interface.

There are many benefits to expressing the type schema of an application in a language-neutral and inter-operable way. One of them is the ability to generate type-safe data layer code, which makes development faster and safer.

Protobufopen in new window is well oriented towards schema migrations, while at the same time being compact and efficient on the wire and in-memory.

Consider this expression of schema declared in protobufopen in new window:

syntax = "proto3";

package example.tasks;

message Task {
  option (object) = true;

  string title = 1;
  bool completed = 2;
}

message TaskList {
  option (object) = true;

  string title = 1;
  repeated Task tasks = 2;
}





 






 




Using a tool called dxtype from @dxos/echo-schema classes can be generated for use with DXOS Client.

dxtype <input protobuf file> <output typescript file>

Note

Note the directives option (object) = true; which instruct the framework to generate TypeScript classes from the marked messages.

Tip

If you're using one of the DXOS application templates, this type generation step is pre-configured as a prebuildopen in new window script for you.

See TypeScript output from `dxtype`

The output is a typescript file that looks roughly like this:

import { TypedObject, TypeFilter, EchoSchema } from '@dxos/react-client';

export const schema = EchoSchema.fromJson(
  '{ "protobuf generated json here": true }'
);

export class Task extends TypedObject {
  static readonly type = schema.getType('example.tasks.Task');

  static filter(opts?: {
    title?: string;
    completed?: boolean;
  }): TypeFilter<Task> {
    return Task.type.createFilter(opts);
  }

  constructor(opts?: { title?: string; completed?: boolean }) {
    super({ ...opts, '@type': Task.type.name }, Task.type);
  }

  declare title: string;
  declare completed: boolean;
}

Declared are the ancestor class and specific fields on the type.

There are other utilities like a filter you can pass to useQuery to locate items of this type.

To use the type declarations, simply import the relevant type like Task from the typescript location out of dxtype and pass it to query<T>:

import { Client } from '@dxos/client';
import { Task } from './schema';

const client = new Client();

async () => {
  await client.initialize();
  // get a list of all spaces
  const spaces = client.spaces.get();
  // grab a space
  const space = spaces[0];
  // get items that match a filter: type inferred from Task.filter()
  const tasks = space.db.query(Task.filter());
};

The resulting collection is an iterable like Task[].