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

Workers e testes

Workers movem trabalho de persistência para fora da thread da página. A fronteira é baseada em mensagens, então toda requisição e resposta precisa ser dado puro compatível com structured clone.

Fluxo de requisição do worker

Demo completa de worker-host na mesma thread

Este playground usa MessageChannel para a documentação executar o protocolo de worker sem criar um arquivo separado de Worker. Em produção, uma app chamaria createWorkerHost() dentro do módulo Worker e manteria createRouter() na thread da página.

Fluxo com worker client

Use createWorkerHost, createRouter e createWorkerClient com MessageChannel.

const channel = new MessageChannel();
channel.port1.start?.();
channel.port2.start?.();

const broadcasts = [];
const router = cana.createRouter({
  port: channel.port1,
  timeoutMs: 5000,
  onBroadcast(event) {
    broadcasts.push({
      cursor: event.cursor,
      type: event.type,
      store: event.store,
      key: event.key
    });
  }
});

const host = cana.createWorkerHost({
  port: channel.port2,
  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' }
      ]
    }
  ]
},
  originId: 'docs-worker-host',
  retainedEvents: 20,
  operationLedger: true
});

const workerClient = cana.createWorkerClient(router);

try {
  await workerClient.open();
  const now = Date.now();
  await workerClient.put('categories', {
    id: 'work',
    name: 'Work',
    color: '#2563eb',
    createdAt: now,
    updatedAt: now
  });
  await workerClient.add('tasks', {
    id: 'task-worker-1',
    title: 'Persist through the worker boundary',
    categoryId: 'work',
    completed: false,
    priority: 'medium',
    createdAt: now,
    updatedAt: now
  });
  const tasks = await workerClient.query('tasks', {
    index: 'byCategory',
    equals: 'work'
  });
  const count = await workerClient.count('tasks');
  return {
    ping: await workerClient.ping(),
    count,
    tasks,
    broadcasts
  };
} finally {
  await workerClient.close();
  router.dispose();
  await host.dispose();
  channel.port1.close();
  channel.port2.close();
}

Layout de arquivos em produção

src/cana.worker.ts

import { createWorkerHost, type CanaSchema } from '@jumentix/cana';

const schema: CanaSchema = {
  version: 1,
  stores: [
    { name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name' }] },
    {
      name: 'tasks',
      keyPath: 'id',
      indexes: [
        { name: 'byCategory', keyPath: 'categoryId' },
        { name: 'byUpdatedAt', keyPath: 'updatedAt' }
      ]
    }
  ]
};

createWorkerHost({
  port: self,
  name: 'tasks-worker-db',
  schema,
  originId: 'tasks-worker',
  retainedEvents: 100,
  operationLedger: true
});

src/cana-client.ts

import {
  createRouter,
  createWorkerClient,
  type CanaChangeEvent
} from '@jumentix/cana';

const worker = new Worker(new URL('./cana.worker.ts', import.meta.url), {
  type: 'module'
});

const events: CanaChangeEvent[] = [];
const router = createRouter({
  port: worker,
  timeoutMs: 15_000,
  onBroadcast(event) {
    events.push(event as CanaChangeEvent);
  }
});

export const canaWorker = createWorkerClient(router);
export const canaWorkerEvents = events;

export async function stopCanaWorker() {
  await canaWorker.close();
  router.dispose();
  worker.terminate();
}

src/tasks.ts

import { canaWorker } from './cana-client';

export async function createTaskInWorker() {
  await canaWorker.open();
  await canaWorker.put('categories', {
    id: 'work',
    name: 'Work',
    color: '#2563eb',
    createdAt: Date.now(),
    updatedAt: Date.now()
  });
  await canaWorker.add('tasks', {
    id: crypto.randomUUID(),
    title: 'Persist through the worker',
    categoryId: 'work',
    completed: false,
    priority: 'medium',
    createdAt: Date.now(),
    updatedAt: Date.now()
  });
  return canaWorker.query('tasks', { index: 'byCategory', equals: 'work' });
}

Estratégia de testes

CamadaO que testarFerramenta
UnitBuilders de schema, mappers de registro, reducers de evento.Bun/Jest sem navegador.
Componente no browserComponentes React/Vue atualizam a partir de CanaChangeEvent.Testing Library com eventos injetados.
Integração IndexedDBOpen, upgrade, query, transaction, avaliação de storage, export/import.Cypress ou automação de browser.
Integração de workerTimeout do router, broadcasts, falhas de structured clone.Worker real em teste de browser.
PerformanceProporções de query, count vs leitura completa, paginação profunda.Suíte de performance no browser.

Próximo

Continue na referência de API.