Apps

Bea.db: queries do app

Bea.db: queries do app

Toda app no theo tem um banco PostgreSQL próprio. Você acessa via db (passado como prop pro componente do app) ou Bea.db (alias global).

Como funciona

Criar tabelas

useEffect(() => {
  db.execute(`
    CREATE TABLE IF NOT EXISTS app_clientes_x7m2 (
      id SERIAL PRIMARY KEY,
      owner_id TEXT NOT NULL,
      nome TEXT NOT NULL,
      telefone TEXT,
      created_at TIMESTAMPTZ DEFAULT now()
    )
  `);
  db.execute(`CREATE INDEX IF NOT EXISTS idx_clientes_owner_x7m2 ON app_clientes_x7m2(owner_id)`);
}, []);
Convenção de nomes de tabela

Nomes seguem app_<entidade>_<hash> onde hash é único do app (4-5 chars). A IA gera automaticamente. Nunca compartilhe tabelas entre apps sem usar App Connections.

Coluna owner_id sempre

SEMPRE inclua owner_id TEXT NOT NULL em toda tabela e filtre nas queries — RLS no banco depende disso. Sem owner_id, dados vazam entre usuários.

Ler dados

const rows = await db.query(
  "SELECT * FROM app_clientes_x7m2 WHERE owner_id = $1 ORDER BY nome",
  [user.id]
);
setClientes(rows);

Com busca (sempre parametrize, nunca concatene):

await db.query(
  "SELECT * FROM app_clientes_x7m2 WHERE owner_id = $1 AND nome ILIKE $2",
  [user.id, `%${busca}%`]
);
SQL injection

Nunca use template literal ${nome} direto na query. Use $1, $2, ... e passe valores no array. SQL injection é problema real mesmo no app gerado por IA.

Inserir / atualizar / deletar

// Insert
await db.execute(
  "INSERT INTO app_clientes_x7m2 (owner_id, nome, telefone) VALUES ($1, $2, $3)",
  [user.id, nome, tel]
);

// Update — sempre filtra por owner_id pra não mexer em dados de outros
await db.execute(
  "UPDATE app_clientes_x7m2 SET telefone = $1 WHERE id = $2 AND owner_id = $3",
  [novoTel, id, user.id]
);

// Delete
await db.execute(
  "DELETE FROM app_clientes_x7m2 WHERE id = $1 AND owner_id = $2",
  [id, user.id]
);

Diferença: query vs execute

FunçãoRetornaUse pra
db.query(sql, params)Array de objetos [{...}, {...}]SELECT, COUNT, retornos genéricos
db.execute(sql, params)Apenas { ok: true }INSERT, UPDATE, DELETE, CREATE, ALTER

Mesmo INSERT ... RETURNING * use com query (porque você quer o resultado).

Limites

  • Timeout de 10s por query
  • 240 requests/min por app (proteção contra loop) — se ultrapassar, banner aparece pedindo pra revisar useEffect
  • Apenas tabelas do próprio app — cross-app só via App Connections
  • Comandos bloqueados: DROP TABLE em tabela de outro app, TRUNCATE, GRANT, qualquer DDL fora do schema do app
Rate limit em loop

Se você ver banner "Loop detectado: app excedeu 240 queries/min", causa típica é useEffect com db.query que dispara setState sem dependency array correto, OU setInterval sem cleanup. Verifique nesses dois lugares.

Debug — DB Explorer

Admin pode acessar SidebarAdminDatabase pra ver tabelas e dados de qualquer app. Útil pra:

  • Confirmar que dados estão chegando
  • Ver schema real (vs. o que o LLM gerou)
  • Limpar dados de teste

Próximo: Bea.* helpers úteis →

Tabelas compartilhadas (sistemas modulares)

Em sistemas modulares, sub-apps acessam tabelas declaradas no app pai via prefixo app_<parent-slug>_shared_<entity>. Acesso é read + write entre toda a família.

// Sub-app "Pipeline" lendo + escrevendo na tabela compartilhada
const clientes = await db.query("SELECT * FROM app_crm_shared_clientes ORDER BY nome");
await db.execute("INSERT INTO app_crm_shared_oportunidades (cliente_id, valor) VALUES ($1, $2)", [clienteId, valor]);

Declarado em /app/<pai>/data-model. Veja Modelo de dados.

Cross-app reads (siblings)

Apps no mesmo negócio (não modular) podem fazer SELECT uns nos outros (read-only):

// App "Faturas" lendo tabela do app "Clientes" (mesmo business)
const clientes = await db.query("SELECT * FROM app_clientes_x7k2");

Diferença: shared tables (modular) = read+write. Sibling tables (business) = read-only.