Joins¶
Pra combinar tabelas, o tempest-db-js tem join(...) — e o resultado é um tipo
composto: um objeto com uma chave por tabela, cada uma com sua linha tipada.
Passo 1 — O join básico¶
Comece pela tabela base com um alias, depois junte outras:
import { join } from "tempest-db-js";
const q = join(User, "user")
.innerJoin(Order, "order", { "user.id": "order.userId" })
.where({ "order.status": "paid" });
O resultado de q é inferido como:
Uma chave por alias, cada uma com a linha completa daquela tabela. Sem achatar
colunas, sem colisão de nomes (user.id e order.id convivem).
Passo 2 — Referências alias.column tipadas¶
on, where e orderBy usam refs no formato "alias.column" — e elas são
checadas em tempo de compilação:
join(User, "user")
.innerJoin(Order, "order", { "user.id": "order.userId" }) // ✅ colunas válidas
.where({ "order.status": "paid" }) // ✅
.orderBy("order.amount", "desc"); // ✅
// ❌ erro: `user.bogus` não é coluna de User
join(User, "user").innerJoin(Order, "order", { "user.bogus": "order.userId" });
Passo 3 — leftJoin e nullability¶
Um leftJoin mantém as linhas da esquerda mesmo sem correspondência — então o lado
direito pode ser nulo. O tipo reflete isso: a chave joinada vira Row | null:
const q = join(User, "user").leftJoin(Order, "order", { "user.id": "order.userId" });
// resultado: { user: UserRow; order: OrderRow | null }[]
Na execução, uma linha sem match traz order: null — e o tipo te obriga a tratar
isso:
const rows = await session.execute(q).all();
for (const row of rows) {
if (row.order) {
console.log(row.user.name, row.order.amount); // `order` estreitado pra OrderRow
} else {
console.log(row.user.name, "sem pedidos");
}
}
Passo 4 — Vários joins¶
Encadeie quantos precisar; cada um adiciona uma chave ao tipo composto:
const q = join(User, "user")
.innerJoin(Order, "order", { "user.id": "order.userId" })
.leftJoin(Product, "product", { "order.productId": "product.id" });
// resultado: { user: UserRow; order: OrderRow; product: ProductRow | null }[]
Como funciona por baixo¶
O dialeto compila as colunas com alias ("user"."id" AS "user.id"), então a
linha plana do driver é dividida de volta em { user: {...}, order: {...} },
coagindo cada lado pelo seu modelo. Pra leftJoin, quando todas as colunas do lado
direito vêm nulas, aquele lado vira null.
Relations declarativas como alternativa
Joins te dão o tipo composto { user, order }. Quando você prefere navegar
relações (user.posts, post.author) sem montar o join na mão, use
hasMany/belongsTo + loadRelations — eager-load tipado, uma query por
relação, sem N+1. Veja Repository.
Limitação atual do where de join
Os combinadores and/or/not funcionam no where de join, mas os
operadores tipados-por-coluna ainda não se aplicam ali: as chaves
alias.column são checadas, e o valor aceita match exato ou operador, porém sem
a restrição por tipo que o select tem. É o próximo refinamento de joins.
Recap¶
join(Model, alias)inicia;.innerJoin/.leftJoin(Model, alias, on)agregam.- Resultado é composto:
{ [alias]: Row }, uma chave por tabela. leftJointorna o lado nullable (Row | null) — o tipo te força a tratar.on/where/orderByusam refsalias.columntipadas.
Você cobriu o caminho principal do tempest-db-js! Veja a Referência pra API completa e o Roadmap pro que vem (migrações, relations).