Repositories
Repositories provide type-safe CRUD operations. Use @repository decorator with both model and dataSource for auto-discovery.
Pattern 1: Zero Boilerplate (Recommended)
The simplest approach - everything is auto-resolved:
// src/repositories/configuration.repository.ts
import { Configuration } from '@/models/entities';
import { PostgresDataSource } from '@/datasources/postgres.datasource';
import { DefaultCRUDRepository, repository } from '@venizia/ignis';
@repository({
model: Configuration,
dataSource: PostgresDataSource,
})
export class ConfigurationRepository extends DefaultCRUDRepository<typeof Configuration.schema> {
// No constructor needed!
async findByCode(opts: { code: string }) {
return this.findOne({ filter: { where: { code: opts.code } } });
}
async findByGroup(opts: { group: string }) {
return this.find({ filter: { where: { group: opts.group } } });
}
}Pattern 2: Explicit @inject
When you need constructor control (e.g., read-only repository or additional dependencies):
// src/repositories/user.repository.ts
import { User } from '@/models/entities';
import { PostgresDataSource } from '@/datasources/postgres.datasource';
import { inject, ReadableRepository, repository } from '@venizia/ignis';
import { CacheService } from '@/services/cache.service';
@repository({ model: User, dataSource: PostgresDataSource })
export class UserRepository extends ReadableRepository<typeof User.schema> {
constructor(
// First parameter MUST be DataSource injection
@inject({ key: 'datasources.PostgresDataSource' })
dataSource: PostgresDataSource, // Must be concrete type, not 'any'
// After first arg, you can inject any additional dependencies
@inject({ key: 'some.cache' })
private cache: SomeCache,
) {
super(dataSource);
}
async findByRealm(opts: { realm: string }) {
// Use injected dependencies
const cached = await this.cache.get(`user:realm:${opts.realm}`);
if (cached) {
return cached;
}
return this.findOne({ filter: { where: { realm: opts.realm } } });
}
}Important:
- First constructor parameter MUST be the DataSource injection
- After the first argument, you can inject any additional dependencies you need
- When
@injectis at param index 0, auto-injection is skipped
Repository Hierarchy
AbstractRepository (base + mixins: FieldsVisibilityMixin + DefaultFilterMixin)
↓
ReadableRepository (read-only: find, findOne, findById, count, existsWith)
↓
PersistableRepository (+ create, updateById, updateAll)
↓
DefaultCRUDRepository (+ deleteById, deleteAll) — recommended default
↓
SoftDeletableRepository (overrides delete to set deletedAt timestamp)| Type | Description |
|---|---|
ReadableRepository | Read-only operations. Write operations throw errors. |
PersistableRepository | Read + write operations (create, update). Extends ReadableRepository. |
DefaultCRUDRepository | Full CRUD including delete. Extends PersistableRepository. Recommended for most use cases. |
SoftDeletableRepository | Extends DefaultCRUDRepository. Overrides delete to set deletedAt timestamp instead of physically removing records. |
Querying Data
For advanced filtering with operators like gt, lt, like, in, between, and more, see Filter System.
const repo = this.get<ConfigurationRepository>({ key: 'repositories.ConfigurationRepository' });
// Find multiple records
const configs = await repo.find({
filter: {
where: { group: 'SYSTEM' },
limit: 10,
order: ['createdAt DESC'],
}
});
// Find one record
const config = await repo.findOne({
filter: { where: { code: 'APP_NAME' } }
});
// Select specific fields (array format)
const configCodes = await repo.find({
filter: {
fields: ['id', 'code', 'group'], // Only these fields returned
limit: 100,
}
});
// Order by JSON/JSONB nested fields
const sorted = await repo.find({
filter: {
order: ['metadata.priority DESC', 'createdAt ASC'],
}
});
// Create a record
const newConfig = await repo.create({
data: {
code: 'NEW_SETTING',
group: 'SYSTEM',
description: 'A new setting',
}
});
// Update by ID
await repo.updateById({
id: 'uuid-here',
data: { description: 'Updated description' }
});
// Delete by ID
await repo.deleteById({ id: 'uuid-here' });Extra Options
All repository operations accept an options parameter with these fields:
| Option | Type | Description |
|---|---|---|
transaction | ITransaction | Transaction context for atomic operations |
shouldReturn | boolean | Whether to return created/updated data (default: true) |
shouldQueryRange | boolean | Return { data, range: { total, skip, limit } } for pagination |
shouldSkipDefaultFilter | boolean | Bypass the model's default filter (e.g., soft delete) |
// Create without returning data (faster)
await repo.create({
data: { code: 'SETTING', group: 'SYSTEM' },
options: { shouldReturn: false },
});
// Bulk create multiple records
await repo.createAll({
data: [
{ code: 'SETTING_A', group: 'SYSTEM' },
{ code: 'SETTING_B', group: 'SYSTEM' },
],
});
// Query with pagination range
const result = await repo.find({
filter: { limit: 20, skip: 0 },
options: { shouldQueryRange: true }
});
// result = { data: [...], range: { total: 150, skip: 0, limit: 20 } }Querying with Relations
Use include to fetch related data. The relation name must match what you defined in static relations:
const configWithCreator = await repo.findOne({
filter: {
where: { code: 'APP_NAME' },
include: [{ relation: 'creator' }],
},
});
console.log('Created by:', configWithCreator.creator.name);Registering Repositories
// src/application.ts
export class Application extends BaseApplication {
preConfigure(): ValueOrPromise<void> {
this.dataSource(PostgresDataSource);
this.repository(UserRepository);
this.repository(ConfigurationRepository);
}
}SoftDeletableRepository
For soft-delete patterns, use SoftDeletableRepository which overrides delete operations to set a deletedAt timestamp instead of physically removing records:
import { SoftDeletableRepository, repository, model, BaseEntity } from '@venizia/ignis';
import { pgTable, timestamp } from 'drizzle-orm/pg-core';
@model({
type: 'entity',
settings: {
hiddenProperties: ['deletedAt'],
defaultFilter: { where: { deletedAt: null } },
},
})
export class Category extends BaseEntity<typeof Category.schema> {
static override schema = pgTable('Category', {
...generateIdColumnDefs({ id: { dataType: 'string' } }),
...generateTzColumnDefs(),
deletedAt: timestamp('deleted_at', { withTimezone: true }),
name: text('name').notNull(),
});
}
@repository({ dataSource: PostgresDataSource, model: Category })
export class CategoryRepository extends SoftDeletableRepository<typeof Category.schema> {}Repository Template
import { DefaultCRUDRepository, repository } from '@venizia/ignis';
import { MyModel } from '@/models/entities';
import { PostgresDataSource } from '@/datasources/postgres.datasource';
@repository({ model: MyModel, dataSource: PostgresDataSource })
export class MyModelRepository extends DefaultCRUDRepository<typeof MyModel.schema> {}Advanced Topics
Performance: Core API Optimization
Ignis automatically optimizes "flat" queries (no relations, no field selection) by using Drizzle's Core API. This provides ~15-20% faster queries for simple reads. The canUseCoreAPI() method on ReadableRepository determines when this optimization applies.
Modular Persistence with Components
Bundle related persistence resources into Components for better organization:
export class UserManagementComponent extends BaseComponent {
override binding() {
this._application.dataSource(PostgresDataSource);
this._application.repository(UserRepository);
this._application.repository(ProfileRepository);
}
}Deep Dive: See Repository Reference for filtering operators, relations, JSON path queries, and array operators.
See Also
Related Concepts:
- Models - Entity definitions used by repositories
- DataSources - Database connections
- Services - Use repositories for data access
- Transactions - Multi-operation consistency
References:
- Repositories API - Complete API reference
- Filter System - Query operators and filtering
- Relations & Includes - Loading related data
- Advanced Features - JSON queries, performance tuning
- Repository Mixins - Soft delete and auditing
Best Practices:
- Data Modeling - Repository design patterns
- Performance Optimization - Query optimization
Tutorials:
- Building a CRUD API - Repository examples
- E-commerce API - Advanced queries and relations