Pular para o conteúdo

Repository API

O Repository<T> é a interface principal para interagir com o banco. Você o obtém a partir da conexão:

const repo = conn.getRepository(User);

Cada entidade tem seu próprio repositório tipado. Todos os métodos são assíncronos.


import { Connection } from 'mirror-orm';
const conn = await Connection.postgres('postgresql://...');
const repo = conn.getRepository(User);

Retorna um array de entidades. Aceita filtros, ordenação, paginação, relações e mais.

const users = await repo.find({
where: { active: true },
orderBy: { createdAt: 'DESC' },
limit: 20,
offset: 0,
relations: ['profile'],
});

Opções disponíveis (IFindOptions<T>):

OpçãoTipoDescrição
wherePartial<T> | Array<Partial<T>>Filtros de busca
orderByPartial<Record<keyof T, 'ASC' | 'DESC'>>Ordenação
limitnumberMáximo de resultados
offsetnumberQuantos registros pular
relationsstring[]Relações a carregar
select(keyof T)[]Colunas específicas
withDeletedbooleanIncluir registros com soft delete
lock'pessimistic_write' | 'pessimistic_read'Lock de linha
filtersstring[]Filtros nomeados da entidade

Igual ao find, mas retorna T | null. Internamente adiciona LIMIT 1.

const user = await repo.findOne({ where: { email: 'dev@example.com' } });
if (user) {
console.log(user.name);
}

Igual ao findOne, mas lança EntityNotFoundError se nenhum resultado for encontrado. Útil quando a ausência do registro é um erro de lógica, não um estado válido.

// Lança EntityNotFoundError se não existir
const user = await repo.findOneOrFail({ where: { id: 1 } });

Atalho para busca pela chave primária.

const user = await repo.findById(1);
const user = await repo.findById('uuid-aqui');

Retorna todos os registros da tabela, sem filtros. Respeita soft delete automaticamente.

const all = await repo.findAll();

Retorna [dados, total] em uma única operação — útil para paginação manual.

const [users, total] = await repo.findAndCount({
where: { active: true },
limit: 10,
offset: 20,
});
console.log(`Exibindo ${users.length} de ${total}`);

Versão de alto nível da paginação. Recebe page e limit e retorna um objeto com data e meta.

const result = await repo.findPaginated({
where: { active: true },
orderBy: { createdAt: 'DESC' },
page: 2,
limit: 10,
});
console.log(result.data); // Array<User>
console.log(result.meta.total); // total de registros
console.log(result.meta.lastPage); // última página
console.log(result.meta.page); // página atual
console.log(result.meta.limit); // itens por página

Retorna um AsyncGenerator<T> para processar grandes volumes de dados sem carregar tudo na memória.

for await (const user of repo.findStream({ where: { active: true } })) {
await processUser(user);
}

Retorna o número de registros que correspondem ao filtro.

const total = await repo.count({ active: true });
const all = await repo.count(); // sem filtro

Retorna true se ao menos um registro corresponder ao filtro. Mais eficiente que count() > 0.

const taken = await repo.exists({ email: 'dev@example.com' });

Insere ou atualiza uma entidade. O Mirror detecta automaticamente a operação correta:

  • Sem PKINSERT
  • Com PKUPDATE apenas das colunas que mudaram (dirty checking)
// INSERT — id não está definido
const user = new User();
user.name = 'Alice';
user.email = 'alice@example.com';
const saved = await repo.save(user);
// saved.id agora está preenchido
// UPDATE — só as colunas alteradas vão no SQL
saved.name = 'Alice Smith';
await repo.save(saved);
// UPDATE users SET name = $1, updated_at = $2 WHERE id = $3

Dirty Checking: O Mirror tira um snapshot das colunas no momento do find. No save, compara o estado atual com o snapshot e inclui no UPDATE apenas o que mudou. Se nada mudou, nenhuma query é disparada.

const user = await repo.findById(1);
await repo.save(user); // nenhuma query — nada mudou

Retorno: a mesma instância mutada (com PK, timestamps e version atualizados).


Versão em lote do save. Processa cada entidade individualmente com dirty checking.

const saved = await repo.saveMany([user1, user2, user3]);

Atualiza colunas diretamente por filtro, sem carregar entidades. Retorna o número de linhas afetadas.

const affected = await repo.update(
{ active: false },
{ lastLoginAt: LessThan(thirtyDaysAgo) }
);

Diferente de save(), o update() não passa pelo dirty checking nem dispara lifecycle hooks.


INSERT ... ON CONFLICT DO UPDATE. Útil para sincronização de dados externos.

await repo.upsert(
product,
['sku'], // coluna(s) de conflito
{ update: ['price', 'stock'] } // colunas a atualizar no conflito (padrão: todas)
);

Se update for omitido, todas as colunas (exceto PK e createdAt) são atualizadas no conflito.


Remove uma entidade pelo PK. Se a entidade tiver @DeletedAt, faz soft delete em vez de hard delete.

const user = await repo.findById(1);
await repo.remove(user);
// Se User tiver @DeletedAt: UPDATE users SET deleted_at = NOW() WHERE id = 1
// Se não tiver: DELETE FROM users WHERE id = 1

Lança MissingPrimaryKeyError se a entidade não tiver PK.


Versão em lote do remove.

await repo.removeMany([user1, user2]);

Remove por filtro, sem precisar carregar as entidades. Retorna o número de linhas afetadas. Sempre faz hard delete, independente de @DeletedAt.

const deleted = await repo.delete({ active: false });
console.log(`${deleted} registros removidos`);

Use remove() quando precisar de soft delete ou cascades. Use delete() quando quiser deletar em massa de forma direta.


Restaura um registro com soft delete, definindo deletedAt = null. Retorna a entidade restaurada.

const user = await repo.findOne({
where: { id: 1 },
withDeleted: true,
});
const restored = await repo.softRestore(user);
// restored.deletedAt === null

Condições simples usam igualdade. Para comparações mais complexas, use os operadores:

import { MoreThan, Like, In, IsNull, Between } from 'mirror-orm';
await repo.find({
where: {
age: MoreThan(18),
name: Like('%silva%'),
status: In(['active', 'pending']),
deletedAt: IsNull(),
score: Between(80, 100),
}
});

Para condições OR, passe um array de objetos — cada objeto é um grupo AND, os grupos são unidos com OR:

await repo.find({
where: [
{ role: 'admin' },
{ role: 'moderator', active: true },
]
// WHERE (role = 'admin') OR (role = 'moderator' AND active = true)
});

Ver Filters & Operators para a referência completa de operadores.


await repo.find({ relations: ['author', 'tags'] });

Suporta dot notation para relações aninhadas:

await repo.find({
relations: ['author', 'author.publisher', 'comments.author'],
});

Cada relação pode gerar queries adicionais dependendo do tipo. Ver Relacionamentos para detalhes.


Por padrão, todas as colunas (exceto select: false) são incluídas. Para buscar apenas algumas:

await repo.find({
select: ['id', 'name', 'email'],
});

Quando select é usado, ele define exatamente quais colunas são retornadas — inclusive sobrescreve select: false nas colunas listadas.


await repo.find({
orderBy: {
createdAt: 'DESC',
name: 'ASC',
}
});

Usado dentro de transações para controle de concorrência pessimista:

await conn.transaction(async (tx) => {
const repo = tx.getRepository(Product);
const product = await repo.findOne({
where: { id: 1 },
lock: 'pessimistic_write', // FOR UPDATE
});
product.stock -= 1;
await repo.save(product);
});
ValorSQL geradoUso
'pessimistic_write'FOR UPDATELeitura exclusiva — bloqueia outras escritas e leituras
'pessimistic_read'FOR SHARELeitura compartilhada — bloqueia outras escritas

Ver também: Transactions e Optimistic Locking.


Aplica filtros globais definidos no @Entity:

@Entity('posts', {
filters: {
published: { status: 'published' },
recent: { createdAt: MoreThan(sevenDaysAgo) },
}
})
class Post { ... }
// Ativar por nome
await repo.find({ filters: ['published', 'recent'] });

Útil para condições recorrentes como multi-tenancy, status de publicação ou soft delete customizado.


MétodoRetornoNotas
find(options?)T[]
findOne(options?)T | null
findOneOrFail(options?)TLança EntityNotFoundError
findById(id)T | nullAtalho por PK
findAll()T[]Sem filtros
findAndCount(options?)[T[], number]
findPaginated(options)IPaginatedResult<T>Inclui meta de paginação
findStream(options?)AsyncGenerator<T>Baixo consumo de memória
count(where?)number
exists(where?)boolean
save(entity)TInsert ou update com dirty check
saveMany(entities)T[]
update(data, where)numberAfetados, sem hooks
upsert(entity, keys, opts?)TON CONFLICT
remove(entity)voidSoft delete automático se tiver @DeletedAt
removeMany(entities)void
delete(where)numberHard delete por filtro
softRestore(entity)T