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 query | Modelo de complexidade | Observações |
|---|---|---|
get(key) por chave primária | Modelo comum de lookup por chave no IndexedDB: O(log n). | O browser controla os detalhes da árvore. |
equals indexado | Modelo comum de índice IndexedDB: O(log n + matches). | O tamanho do resultado ainda importa. |
limit: n depois do cursor abrir | O(limit). | O Cana não materializa a store inteira. |
offset + limit | O(offset + limit). | Movimento de cursor é o custo. |
count() nativo | Uma 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.
### Query + explain
```ts
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 };
```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.