Skip to Content
PortuguêsDocumentação JumentixPacotes@jumentix/canaGuia de usoQueries e planos

Queries e planos

O Cana mantém queries pequenas de propósito: escolha uma tabela, opcionalmente escolha um índice, depois restrinja o cursor com equals, limit, offset e direção.

Query indexada de tarefas

type Task = {
  id: string;
  title: string;
  categoryId: string;
  completed: boolean;
  priority: 'low' | 'medium' | 'high';
  createdAt: number;
  updatedAt: number;
};

const workTasks = await client.table<Task>('tasks').query({
  index: 'byCategory',
  equals: 'work',
  direction: 'next',
  limit: 20
});

console.log(workTasks.map((task) => task.title));

Não existe filter(record => record.completed) baseado em expressão. Filtrar em JavaScript força leitura completa e esconde problemas de performance. Adicione um índice como byCompleted quando a query fizer parte do fluxo do usuário.

Paginação

const firstPage = await client.table<Task>('tasks').query({
  index: 'byUpdatedAt',
  direction: 'next',
  offset: 0,
  limit: 20
});

const secondPage = await client.table<Task>('tasks').query({
  index: 'byUpdatedAt',
  direction: 'next',
  offset: 20,
  limit: 20
});

console.log({
  firstPageSize: firstPage.length,
  secondPageSize: secondPage.length
});

Paginação profunda por offset avança o cursor IndexedDB. Ela não clona os registros pulados para um array JavaScript, mas o cursor ainda precisa andar, o que deixa o formato em O(offset + limit).

Count e explain

const openCount = await client.table<Task>('tasks').count({
  index: 'byCompleted',
  equals: false
});

const explained = await client.table<Task>('tasks').explain({
  index: 'byCategory',
  equals: 'work'
});

console.log({
  openCount,
  plan: explained.plan,
  records: explained.records
});

Mapa rápido de complexidade

Forma de queryModelo de complexidadeObservações
get(key) por chave primáriaModelo comum de lookup por chave no IndexedDB: O(log n).O browser controla os detalhes da árvore.
equals indexadoModelo comum de índice IndexedDB: O(log n + matches).O tamanho do resultado ainda importa.
limit: n depois do cursor abrirO(limit).O Cana não materializa a store inteira.
offset + limitO(offset + limit).Movimento de cursor é o custo.
count() nativoUma requisição nativa.O Cana não lê cada registro para JavaScript.

Execute aqui

Query + explain

Rode uma query indexada de Task por Category e inspecione o plano.

const client = cana.createClient({
  name: dbName,
  schema: {
  version: 1,
  stores: [
    { name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
    {
      name: 'tasks',
      keyPath: 'id',
      indexes: [
        { name: 'byCategory', keyPath: 'categoryId' },
        { name: 'byCompleted', keyPath: 'completed' },
        { name: 'byUpdatedAt', keyPath: 'updatedAt' }
      ]
    }
  ]
}
});
await client.open();
const now = Date.now();
await client.table('categories').bulkAdd([
  { id: 'work', name: 'Work', color: '#2563eb', createdAt: now, updatedAt: now },
  { id: 'home', name: 'Home', color: '#16a34a', createdAt: now, updatedAt: now }
]);
await client.table('tasks').bulkAdd([
  {
    id: 'task-1',
    title: 'Write the Cana tutorial',
    categoryId: 'work',
    completed: false,
    priority: 'high',
    createdAt: now,
    updatedAt: now
  },
  {
    id: 'task-2',
    title: 'Review category filters',
    categoryId: 'home',
    completed: true,
    priority: 'medium',
    createdAt: now,
    updatedAt: now + 1
  }
]);
const { records, plan } = await client.table('tasks').explain({
  index: 'byCategory',
  equals: 'work'
});
return { records, plan };

Próximo

Continue em transações e eventos de mudança.