Base Classes
Devux provides base classes that your components extend. These handle common functionality like validation, transactions, and performance monitoring automatically.
Use-Case Base Classes
TransactionalUseCase
For endpoints that need database transactions.
@fullInjectable()
export class CreateCustomerUseCase extends TransactionalUseCase<CreateCustomerInput> {
// Required: define transaction isolation level
protected override getIsolationLevel(): TransactionIsolationLevel {
return 'read-committed';
}
// Required: define transaction access mode
protected override getAccessMode(): TransactionAccessMode {
return 'read-write';
}
// Required: access control check
protected override async _assertCanAccess(): Promise<void> {
// Check if user can access this endpoint
}
// Required: your business logic
protected override async _execute(input: CreateCustomerInput): Promise<void> {
// Implementation
}
}Overridable methods:
| Method | Default | Purpose |
|---|---|---|
getTransactionMaxAttempts() | 5 | Max retry attempts on deadlock |
getTransactionBaseDelayMillis() | 50 | Base delay between retries |
getDurationThresholdMillis() | 200 | Slow use-case threshold for logging |
Available methods:
commit()- Manually commit the transactionrollback()- Manually rollback the transaction
NonTransactionalUseCase
For read-only endpoints or those that don't need transactions.
@fullInjectable()
export class GetCustomerUseCase extends NonTransactionalUseCase<GetCustomerInput> {
protected override async _assertCanAccess(): Promise<void> {
// Access control
}
protected override async _execute(input: GetCustomerInput): Promise<void> {
// Implementation - no transaction wrapper
}
}Repo Base Classes
TransactionalRepo
For repos that run within a transaction context.
@fullInjectable()
export class CreateCustomerRepo extends TransactionalRepo<CreateCustomerRepoInput, CreateCustomerRepoOutput> {
// Required: your database operation
protected override async _execute(input: CreateCustomerRepoInput): Promise<CreateCustomerRepoOutput> {
const customer = await this.trx
.insertInto('customers')
.values(input)
.returningAll()
.executeTakeFirstOrThrow();
return { success: true, data: customer };
}
// Auto-generated by CLI
protected override getRepoInputZodSchema() {
return createCustomerRepoInputZodSchema;
}
// Auto-generated by CLI
protected override getRepoOutputZodSchema() {
return createCustomerRepoOutputZodSchema;
}
// Optional: map constraint violations to error codes
protected override getUniqueKeyViolationErrorMap() {
return {
'customers_email_unique': CustomersErrorCodes['CustomerEmailAlreadyExists'],
};
}
}Key property:
this.trx- The transaction connection, shared across all repos in the use-case
Overridable methods:
| Method | Default | Purpose |
|---|---|---|
getDurationThresholdMillis() | 50 | Slow repo threshold for logging |
getUniqueKeyViolationErrorMap() | {} | Map unique constraint names to error codes |
getForeignKeyViolationErrorMap() | {} | Map foreign key constraint names to error codes |
NonTransactionalRepo
For repos that don't need transaction context.
@fullInjectable()
export class GetCustomerRepo extends NonTransactionalRepo<GetCustomerRepoInput, GetCustomerRepoOutput> {
protected override async _execute(input: GetCustomerRepoInput): Promise<GetCustomerRepoOutput> {
const customer = await this.db
.selectFrom('customers')
.where('id', '=', input.customerId)
.selectAll()
.executeTakeFirst();
if (!customer) {
return { success: false, errorCode: CustomersErrorCodes['CustomerNotFound'] };
}
return { success: true, data: customer };
}
}Key property:
this.db- The database connection pool
Domain Service Base Class
DomainServiceBase
For shared business logic across endpoints.
@fullInjectable()
export class CalculatePricingService extends DomainServiceBase<CalculatePricingInput, CalculatePricingOutput> {
protected override async _execute(input: CalculatePricingInput): Promise<CalculatePricingOutput> {
// Business logic
return { success: true, data: { price: 100 } };
}
// Auto-generated by CLI
protected override getServiceInputZodSchema() {
return calculatePricingInputZodSchema;
}
// Auto-generated by CLI
protected override getServiceOutputZodSchema() {
return calculatePricingOutputZodSchema;
}
}Overridable methods:
| Method | Default | Purpose |
|---|---|---|
getDurationThresholdMillis() | 100 | Slow service threshold for logging |
Performance Monitoring
All base classes automatically measure execution time. If an operation exceeds its threshold, the corresponding Core Hook is called:
onSlowUseCase- Use-case exceeded thresholdonSlowRepo- Repo exceeded thresholdonSlowDomainService- Domain service exceeded threshold
Override getDurationThresholdMillis() for operations that legitimately take longer:
protected override getDurationThresholdMillis(): number {
return 500; // This operation is expected to be slower
}Automatic Validation
Repos and domain services automatically validate inputs and outputs against their Zod schemas during development. See Automatic Validation for details.