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

```typescript
// 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):

```typescript
// 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 `@inject` is 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](../../../references/base/filter-system/).

```typescript
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) |

```typescript
// 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`:

```typescript
const configWithCreator = await repo.findOne({
  filter: {
    where: { code: 'APP_NAME' },
    include: [{ relation: 'creator' }],
  },
});

console.log('Created by:', configWithCreator.creator.name);
```

## Registering Repositories

```typescript
// 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:

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

```typescript
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:

```typescript
export class UserManagementComponent extends BaseComponent {
  override binding() {
    this._application.dataSource(PostgresDataSource);
    this._application.repository(UserRepository);
    this._application.repository(ProfileRepository);
  }
}
```

> **Deep Dive:** See [Repository Reference](../../../references/base/repositories/) for filtering operators, relations, JSON path queries, and array operators.

## See Also

- **Related Concepts:**
  - [Models](/guides/core-concepts/persistent/models) - Entity definitions used by repositories
  - [DataSources](/guides/core-concepts/persistent/datasources) - Database connections
  - [Services](/guides/core-concepts/services) - Use repositories for data access
  - [Transactions](/guides/core-concepts/persistent/transactions) - Multi-operation consistency

- **References:**
  - [Repositories API](/references/base/repositories/) - Complete API reference
  - [Filter System](/references/base/filter-system/) - Query operators and filtering
  - [Relations & Includes](/references/base/repositories/relations) - Loading related data
  - [Advanced Features](/references/base/repositories/advanced) - JSON queries, performance tuning
  - [Repository Mixins](/references/base/repositories/mixins) - Soft delete and auditing

- **Best Practices:**
  - [Data Modeling](/best-practices/data-modeling) - Repository design patterns
  - [Performance Optimization](/best-practices/performance-optimization) - Query optimization

- **Tutorials:**
  - [Building a CRUD API](/guides/tutorials/building-a-crud-api) - Repository examples
  - [E-commerce API](/guides/tutorials/ecommerce-api) - Advanced queries and relations
