Storage e recuperação de crash
O Cana armazena dados duráveis no navegador e expõe sinais suficientes para a aplicação decidir quando oferecer backup, limpeza ou reconciliação de dados locais.
Avaliação de storage
import { createClient, type CanaSchema } from '@jumentix/cana';
const schema: CanaSchema = {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id' },
{ name: 'tasks', keyPath: 'id', indexes: [{ name: 'byCategory', keyPath: 'categoryId' }] }
]
};
const client = createClient({
name: 'tasks-storage-demo',
schema
});
await client.open();
const storage = await client.storageState();
const durability = await client.durabilityAssessment();
console.log({
backend: client.backend,
storage,
durability
});Use os sinais de storage e durabilidade para decidir quando a UI deve oferecer export, limpeza ou nova tentativa.
Export e import
const exported = await client.exportAll();
const restored = createClient({
name: 'tasks-restored-demo',
schema
});
await restored.open();
await restored.importAll(exported);
console.log({
categories: await restored.table('categories').query(),
tasks: await restored.table('tasks').query()
});Use export/import para backup controlado pelo usuário, migração entre versões da aplicação ou diagnóstico.
Resolver uma escrita incerta
const ledgerClient = createClient({
name: 'tasks-ledger-demo',
schema,
operationLedger: true
});
await ledgerClient.open();
const tx = await ledgerClient.transaction('readwrite', ['categories', 'tasks'], async (scope) => {
const now = Date.now();
await scope.table('categories').put({
id: 'support',
name: 'Support',
color: '#0f766e',
createdAt: now,
updatedAt: now
});
await scope.table('tasks').put({
id: 'support-1',
title: 'Verify uncertain writes',
categoryId: 'support',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
});
return 'done';
});
const resolved = await ledgerClient.resolveWrite(tx.correlationId, tx.attemptedAt);
console.log({
outcome: tx.outcome,
resolved
});resolveWrite() serve para escritas reportadas como unknown, normalmente
depois que um worker, aba ou conexão morre antes de o caller receber o resultado.
Execute aqui
Avaliacao de storage
Leia storageState e durabilityAssessment depois de gravar Task.
### Avaliacao de storage
```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();
await client.table('categories').add({ id: 'docs', name: 'Docs', color: '#0f766e' });
await client.table('tasks').add({
id: 'docs-1',
title: 'Durable data',
categoryId: 'docs',
completed: false,
priority: 'high',
createdAt: 1,
updatedAt: 1
});
const storage = await client.storageState();
const durability = await client.durabilityAssessment();
return { backend: client.backend, storage, durability };
```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();
await client.table('categories').add({ id: 'docs', name: 'Docs', color: '#0f766e' });
await client.table('tasks').add({
id: 'docs-1',
title: 'Durable data',
categoryId: 'docs',
completed: false,
priority: 'high',
createdAt: 1,
updatedAt: 1
});
const storage = await client.storageState();
const durability = await client.durabilityAssessment();
return { backend: client.backend, storage, durability };Operation ledger
Resolva uma escrita de Task commitada com operation ledger ligado.
### Operation ledger
```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' }
]
}
]
},
operationLedger: true
});
await client.open();
const tx = await client.transaction('readwrite', ['categories', 'tasks'], async (scope) => {
const now = Date.now();
await scope.table('categories').put({ id: 'docs', name: 'Docs', color: '#0f766e' });
await scope.table('tasks').put({
id: 'docs-1',
title: 'Reconcile uncertain write',
categoryId: 'docs',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
});
return 'wrote';
});
const resolved = await client.resolveWrite(tx.correlationId, tx.attemptedAt);
return { outcome: tx.outcome, resolved, task: await client.table('tasks').get('docs-1') };
```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' }
]
}
]
},
operationLedger: true
});
await client.open();
const tx = await client.transaction('readwrite', ['categories', 'tasks'], async (scope) => {
const now = Date.now();
await scope.table('categories').put({ id: 'docs', name: 'Docs', color: '#0f766e' });
await scope.table('tasks').put({
id: 'docs-1',
title: 'Reconcile uncertain write',
categoryId: 'docs',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
});
return 'wrote';
});
const resolved = await client.resolveWrite(tx.correlationId, tx.attemptedAt);
return { outcome: tx.outcome, resolved, task: await client.table('tasks').get('docs-1') };Export
Exporte as stores Category e Task como dados puros.
### Export
```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 dump = await client.exportAll();
return dump;
```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 dump = await client.exportAll();
return dump;Próximo
Continue em workers e testes.