Repository
A Repository is a special Component that is responsible for retrieving data from a database.
It can be used in conjunction with the built-in SQL query builder to build complex queries.
Definition
import { Repository } from "@tymber/core";
interface TodoItem {
id: number;
title: string;
description: string;
dueDate: Date,
completed: boolean;
}
export class TodoRepository extends Repository<number, TodoItem> {
tableName = "todos";
}
Registration
import { type Module, type AppInit } from "@tymber/core";
import { TodoRepository } from "./repositories/TodoRepository";
export const MyModule: Module = {
name: "my-module",
version: "1.2.3",
init(app: AppInit) {
app.component(TodoRepository);
},
};
API
Public methods
findById(ctx, id)
Find a single entity (or undefined) by its ID:
const item = await todoRepository.findById(ctx, todoId);
deleteById(ctx, id)
Delete a single entity by its ID:
try {
await todoRepository.deleteById(ctx, todoId);
} catch (e) {
if (e instanceof EntityNotFoundError) {
// ...
} else {
throw e;
}
}
save(ctx, entity)
Upsert the entity:
const { id } = await todoRepository.save(ctx, todoItem);
The entity is converted to a row with the toRow() method.
startTransaction(ctx)
Start a new transaction:
await todoRepository.startTransaction(ctx, async () => {
// ...
});
If the callback throws an error, all changes are rolled back.
Protected methods
all(ctx, statement)
Return all entities matching the given statement:
interface Query {
page: number;
size: number;
sort:
| "label:asc"
| "label:desc";
}
export class TodoRepository extends Repository<number, TodoItem> {
tableName = "todos";
find(ctx: Context, query: Query) {
const sqlQuery = sql
.select()
.from(this.tableName)
.offset((query.page - 1) * query.size)
.limit(query.size);
switch (query.sort) {
case "label:asc":
sqlQuery.orderBy(["lower(label)"]);
break;
case "label:desc":
sqlQuery.orderBy(["lower(label) desc"]);
break;
}
return this.all(ctx, sqlQuery);
}
}
The statement argument is a SQL query built with Tymber built-in SQL query builder
Each row is converted to an entity with the toEntity() method.
one(ctx, statement)
Return one single entity (or undefined) matching the given statement:
export class TodoRepository extends Repository<number, TodoItem> {
tableName = "todos";
findOneByLabel(ctx: Context, label: string) {
const query = sql
.select()
.from(this.tableName)
.where({ label });
return this.one(ctx, query);
}
}
count(ctx, statement)
Return the number of entities matching the given statement:
export class TodoRepository extends Repository<number, TodoItem> {
tableName = "todos";
find(ctx: Context) {
const countQuery = sql
.select([sql.raw("COUNT(*) as count")])
.from(this.tableName);
return this.count(ctx, countQuery);
}
}
toRow(entity)
Convert an entity to a row. By default, each field is converted from camel case to snake case.
This method can be overridden to customize the conversion:
export class TodoRepository extends Repository<number, TodoItem> {
tableName = "todos";
override toRow(entity: Partial<TodoItem>) {
const row = super.toRow(entity);
// ...
return row;
}
}
toEntity(row)
Convert a row to an entity. By default, each field is converted from snake case to camel case.
This method can be overridden to customize the conversion:
export class TodoRepository extends Repository<number, TodoItem> {
tableName = "todos";
override toEntity(row: Record<string, any>) {
const entity = super.toEntity(row);
// ...
return entity;
}
}
db
A DB instance is automatically injected into each repository:
db.run(ctx, statement)runs a SQL query and returns the number of affected rowsdb.query(ctx, statement)runs a SQL query and returns the result as an array of rows
export class TodoRepository extends Repository<number, TodoItem> {
tableName = "todos";
async deleteExpiredEntries(ctx: Context) {
const { affectedRows } = await this.db.run(
ctx,
sql
.deleteFrom(this.tableName)
.where(sql.raw("due_date < NOW() - INTERVAL '30 days'")),
);
return affectedRows;
}
}