For the complete documentation index, see llms.txt. This page is also available as Markdown.

Repository

A repository is the domain-facing data-access boundary. Use cases and handlers depend on a tech-free port; an adapter implements it for a specific database. Swap databases by writing a new adapter — the domain never changes.

The pattern, by hand

SoapJS is convention-driven (no scaffolding CLI) — you write these pieces directly. The demo is the full reference; here's the shape.

1. Port — the contract + DI token

import { Result } from '@soapjs/soap';
import { Character } from '../../domain/character.entity';

export abstract class CharacterRepository {
  static readonly Token = 'CharacterRepository';
  abstract add(...characters: Character[]): Promise<Result<Character[]>>;
  abstract findById(id: string): Promise<Result<Character | null>>;
  abstract findAll(filters?: CharacterFilters): Promise<Result<Character[]>>;
}

2. Adapter — the implementation

Extend ReadWriteRepository and implement the port. Build queries with Whereno database syntax leaks into the adapter:

import { ReadWriteRepository, FindParams, Where } from '@soapjs/soap';

export class CharacterRepositoryMongo
  extends ReadWriteRepository<Character, CharacterDocument>
  implements CharacterRepository {

  async findById(id: string): Promise<Result<Character | null>> {
    const r = await this.find(FindParams.create({ where: new Where().valueOf('_id').isEq(id), limit: 1 }));
    return r.isFailure() ? Result.withFailure(r.failure) : Result.withSuccess(r.content[0] ?? null);
  }

  async findAll(filters: CharacterFilters = {}): Promise<Result<Character[]>> {
    const where = new Where();
    if (filters.universe) where.valueOf('universe').isEq(filters.universe);
    return this.find(FindParams.create({ where, sort: { name: 1 } }));
  }
}

ReadWriteRepository provides add, find, count, update, remove. Compose queries with Where + FindParams / UpdateParams / RemoveParams.

3. Wire it in the composition root

Handlers inject the port, never the adapter:

Querying

  • Where conditions — typed repo methods (Using Where Conditions)

  • RepositoryQuery — named, reusable query objects (Using QueryBuilder)

  • Native queries — the Source.native() escape hatch (Using Native Queries)

CQRS read/write split

For CQRS, expose two ports — a read repository and a write repository — backed by separate connections (replica vs primary). See the demo.

Last updated