Repository API
Repository<T> is the main interface for interacting with the bank. You get it from connecting:
const repo = conn.getRepository(User);Each entity has its own typed repository. All methods are asynchronous.
Getting a Repository
Section titled “Getting a Repository”import { Connection } from 'mirror-orm';
const conn = await Connection.postgres('postgresql://...');const repo = conn.getRepository(User);Queries
Section titled “Queries”find(options?)
Section titled “find(options?)”Returns an array of entities. Supports filters, sorting, pagination, relationships and more.
const users = await repo.find({ where: { active: true }, orderBy: { createdAt: 'DESC' }, limit: 20, offset: 0, relations: ['profile'],});Available options (IFindOptions<T>):
| Option | Type | Description |
|---|---|---|
where | Partial<T> | Array<Partial<T>> | Search filters |
orderBy | Partial<Record<keyof T, 'ASC' | 'DESC'>> | Ordering |
limit | number | Maximum results |
offset | number | How many records to skip |
relations | string[] | Relationships to load |
select | (keyof T)[] | Specific columns |
withDeleted | boolean | Include records with soft delete |
lock | 'pessimistic_write' | 'pessimistic_read' | Line lock |
filters | string[] | Entity Named Filters |
findOne(options?)
Section titled “findOne(options?)”Same as find, but returns T | null. Internally adds LIMIT 1.
const user = await repo.findOne({ where: { email: 'dev@example.com' } });
if (user) { console.log(user.name);}findOneOrFail(options?)
Section titled “findOneOrFail(options?)”Same as findOne, but throws EntityNotFoundError if no results are found. Useful when the absence of the record is a logic error, not a valid state.
// Lança EntityNotFoundError se não existirconst user = await repo.findOneOrFail({ where: { id: 1 } });findById(id)
Section titled “findById(id)”Shortcut for searching by primary key.
const user = await repo.findById(1);const user = await repo.findById('uuid-aqui');findAll()
Section titled “findAll()”Returns all records in the table, without filters. Respects soft delete automatically.
const all = await repo.findAll();findAndCount(options?)
Section titled “findAndCount(options?)”Returns [dados, total] in a single operation — useful for manual paging.
const [users, total] = await repo.findAndCount({ where: { active: true }, limit: 10, offset: 20,});
console.log(`Exibindo ${users.length} de ${total}`);findPaginated(options)
Section titled “findPaginated(options)”High-level version of pagination. Takes page and limit and returns an object with data and 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 registrosconsole.log(result.meta.lastPage); // última páginaconsole.log(result.meta.page); // página atualconsole.log(result.meta.limit); // itens por páginafindStream(options?)
Section titled “findStream(options?)”Returns a AsyncGenerator<T> to process large volumes of data without loading everything into memory.
for await (const user of repo.findStream({ where: { active: true } })) { await processUser(user);}count(where?)
Section titled “count(where?)”Returns the number of records that match the filter.
const total = await repo.count({ active: true });const all = await repo.count(); // sem filtroexists(where?)
Section titled “exists(where?)”Returns true if at least one record matches the filter. More efficient than count() > 0.
const taken = await repo.exists({ email: 'dev@example.com' });Mutations
Section titled “Mutations”save(entity)
Section titled “save(entity)”Inserts or updates an entity. Mirror automatically detects correct operation:
- No PK →
INSERT - With PK →
UPDATEonly for columns that changed (dirty checking)
// INSERT — id não está definidoconst 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 SQLsaved.name = 'Alice Smith';await repo.save(saved);// UPDATE users SET name = $1, updated_at = $2 WHERE id = $3Dirty Checking: Mirror takes a snapshot of the columns at the time of find. In save, it compares the current state with the snapshot and includes in UPDATE only what has changed. If nothing has changed, no query is fired.
const user = await repo.findById(1);await repo.save(user); // nenhuma query — nada mudouReturn: the same mutated instance (with updated PK, timestamps and version).
saveMany(entities)
Section titled “saveMany(entities)”Batch version of save. Process each entity individually with dirty checking.
const saved = await repo.saveMany([user1, user2, user3]);update(data, where)
Section titled “update(data, where)”Updates columns directly by filter, without loading entities. Returns the number of rows affected.
const affected = await repo.update( { active: false }, { lastLoginAt: LessThan(thirtyDaysAgo) });Unlike
save(),update()does not go through dirty checking or trigger lifecycle hooks.
upsert(entity, conflictKeys, options?)
Section titled “upsert(entity, conflictKeys, options?)”INSERT ... ON CONFLICT DO UPDATE. Useful for synchronizing external data.
await repo.upsert( product, ['sku'], // coluna(s) de conflito { update: ['price', 'stock'] } // colunas a atualizar no conflito (padrão: todas));If update is omitted, all columns (except PK and createdAt) are updated in the conflict.
remove(entity)
Section titled “remove(entity)”Removes an entity by PK. If the entity has @DeletedAt, do soft delete instead of 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 = 1Throws MissingPrimaryKeyError if the entity has no PK.
removeMany(entities)
Section titled “removeMany(entities)”Batch version of remove.
await repo.removeMany([user1, user2]);delete(where)
Section titled “delete(where)”Removes by filter, without having to load the entities. Returns the number of rows affected. Always does hard delete, regardless of @DeletedAt.
const deleted = await repo.delete({ active: false });console.log(`${deleted} registros removidos`);Use
remove()when you need soft delete or cascades. Usedelete()when you want direct mass deletion.
softRestore(entity)
Section titled “softRestore(entity)”Restores a record with soft delete by setting deletedAt = null. Returns the restored entity.
const user = await repo.findOne({ where: { id: 1 }, withDeleted: true,});
const restored = await repo.softRestore(user);// restored.deletedAt === null---## Query Options in Detail
where — Filters
Section titled “where — Filters”Simple conditions use equality. For more complex comparisons, use the operators:
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), }});For OR conditions, pass an array of objects — each object is a AND group, groups are joined with OR:
await repo.find({ where: [ { role: 'admin' }, { role: 'moderator', active: true }, ] // WHERE (role = 'admin') OR (role = 'moderator' AND active = true)});See Filters & Operators for the complete operator reference.
relations — Relationship Loading
Section titled “relations — Relationship Loading”await repo.find({ relations: ['author', 'tags'] });Supports dot notation for nested relationships:
await repo.find({ relations: ['author', 'author.publisher', 'comments.author'],});Each relationship can generate additional queries depending on the type. See Relationships for details.
select — Specific Columns
Section titled “select — Specific Columns”By default, all columns (except select: false) are included. To look for just a few:
await repo.find({ select: ['id', 'name', 'email'],});When
selectis used, it defines exactly which columns are returned — including overwritingselect: falseon the listed columns.
orderBy — Ordering
Section titled “orderBy — Ordering”await repo.find({ orderBy: { createdAt: 'DESC', name: 'ASC', }});lock — Line Locks
Section titled “lock — Line Locks”Used within transactions for pessimistic concurrency control:
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);});| Value | Generated SQL | Usage |
|---|---|---|
'pessimistic_write' | FOR UPDATE | Exclusive read — blocks other writing and reading |
'pessimistic_read' | FOR SHARE | Shared reading — blocks other writing |
See also: Transactions and Optimistic Locking.
filters — Named Filters
Section titled “filters — Named Filters”Applies global filters defined in @Entity:
@Entity('posts', { filters: { published: { status: 'published' }, recent: { createdAt: MoreThan(sevenDaysAgo) }, }})class Post { ... }
// Ativar por nomeawait repo.find({ filters: ['published', 'recent'] });Useful for recurring conditions such as multi-tenancy, publishing status or custom soft delete.
Quick Reference
Section titled “Quick Reference”| Method | Return | Notes |
|---|---|---|
find(options?) | T[] | |
findOne(options?) | T | null | |
findOneOrFail(options?) | T | Releases EntityNotFoundError |
findById(id) | T | null | Shortcut by PK |
findAll() | T[] | No filters |
findAndCount(options?) | [T[], number] | |
findPaginated(options) | IPaginatedResult<T> | Includes pagination target |
findStream(options?) | AsyncGenerator<T> | Low memory consumption |
count(where?) | number | |
exists(where?) | boolean | |
save(entity) | T | Insert or update with dirty check |
saveMany(entities) | T[] | |
update(data, where) | number | Affected, no hooks |
upsert(entity, keys, opts?) | T | ON CONFLICT |
remove(entity) | void | Automatic soft delete if you have @DeletedAt |
removeMany(entities) | void | |
delete(where) | number | Hard delete by filter |
softRestore(entity) | T |