Skip to content

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.


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

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>):

OptionTypeDescription
wherePartial<T> | Array<Partial<T>>Search filters
orderByPartial<Record<keyof T, 'ASC' | 'DESC'>>Ordering
limitnumberMaximum results
offsetnumberHow many records to skip
relationsstring[]Relationships to load
select(keyof T)[]Specific columns
withDeletedbooleanInclude records with soft delete
lock'pessimistic_write' | 'pessimistic_read'Line lock
filtersstring[]Entity Named Filters

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);
}

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 existir
const user = await repo.findOneOrFail({ where: { id: 1 } });

Shortcut for searching by primary key.

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

Returns all records in the table, without filters. Respects soft delete automatically.

const all = await repo.findAll();

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}`);

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 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

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);
}

Returns the number of records that match the filter.

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

Returns true if at least one record matches the filter. More efficient than count() > 0.

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

Inserts or updates an entity. Mirror automatically detects correct operation:

  • No PKINSERT
  • With PKUPDATE only for columns that changed (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: 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 mudou

Return: the same mutated instance (with updated PK, timestamps and version).


Batch version of save. Process each entity individually with dirty checking.

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

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.


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.


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 = 1

Throws MissingPrimaryKeyError if the entity has no PK.


Batch version of remove.

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

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. Use delete() when you want direct mass deletion.


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

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.


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.


By default, all columns (except select: false) are included. To look for just a few:

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

When select is used, it defines exactly which columns are returned — including overwriting select: false on the listed columns.


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

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);
});
ValueGenerated SQLUsage
'pessimistic_write'FOR UPDATEExclusive read — blocks other writing and reading
'pessimistic_read'FOR SHAREShared reading — blocks other writing

See also: Transactions and Optimistic Locking.


Applies global filters defined in @Entity:

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

Useful for recurring conditions such as multi-tenancy, publishing status or custom soft delete.


MethodReturnNotes
find(options?)T[]
findOne(options?)T | null
findOneOrFail(options?)TReleases EntityNotFoundError
findById(id)T | nullShortcut 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)TInsert or update with dirty check
saveMany(entities)T[]
update(data, where)numberAffected, no hooks
upsert(entity, keys, opts?)TON CONFLICT
remove(entity)voidAutomatic soft delete if you have @DeletedAt
removeMany(entities)void
delete(where)numberHard delete by filter
softRestore(entity)T