Skip to content

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.

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

MethodDefaultPurpose
getTransactionMaxAttempts()5Max retry attempts on deadlock
getTransactionBaseDelayMillis()50Base delay between retries
getDurationThresholdMillis()200Slow use-case threshold for logging

Available methods:

  • commit() - Manually commit the transaction
  • rollback() - Manually rollback the transaction

NonTransactionalUseCase

For read-only endpoints or those that don't need transactions.

typescript
@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.

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

MethodDefaultPurpose
getDurationThresholdMillis()50Slow 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.

typescript
@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.

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

MethodDefaultPurpose
getDurationThresholdMillis()100Slow 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 threshold
  • onSlowRepo - Repo exceeded threshold
  • onSlowDomainService - Domain service exceeded threshold

Override getDurationThresholdMillis() for operations that legitimately take longer:

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