npm package: @haykal/core-backend
Description: > Global NestJS infrastructure module providing logging, error handling, database, Redis caching, request context, domain events, query building, and OpenAPI — the foundation every Haykal backend module depends on.
Latest version: 1.0.0

# @haykal/core-backend

> Global NestJS infrastructure module providing logging, error handling, database, Redis caching, request context, domain events, query building, and OpenAPI — the foundation every Haykal backend module depends on.

## Installation

```bash
pnpm add @haykal/core-backend
```

## Configuration

### `forRoot()` Options

```typescript
import { CoreBackendModule } from '@haykal/core-backend';

@Module({
  imports: [
    CoreBackendModule.forRoot({
      appName: 'haykal',
      env: 'development',
      redis: {
        url: 'redis://localhost:6379',
        ttlDefaultSeconds: 300,
        keyPrefix: 'haykal',
      },
      database: {
        host: 'localhost',
        port: 5432,
        username: 'postgres',
        password: 'postgres',
        database: 'haykal',
        poolSize: 10,
        logging: false,
      },
      openApi: {
        enabled: true,
        title: 'Haykal API',
        version: '1.0.0',
        path: '/api/docs',
      },
      logging: {
        level: 'debug',
        json: false,
        excludeRoutes: ['/health', '/ready'],
      },
      cors: {
        origins: ['http://localhost:3000'],
        credentials: true,
      },
      requestTimeoutMs: 30000,
    }),
  ],
})
export class AppModule {}
```

### Configuration Reference

| Property           | Type                                         | Required | Default | Description                                       |
| ------------------ | -------------------------------------------- | -------- | ------- | ------------------------------------------------- |
| `appName`          | `string`                                     | **Yes**  | —       | Application name for logging, cache keys, OpenAPI |
| `env`              | `'development' \| 'staging' \| 'production'` | **Yes**  | —       | Runtime environment                               |
| `redis`            | `RedisConfig`                                | No       | —       | Redis config; caching disabled if omitted         |
| `database`         | `DatabaseConfig`                             | No       | —       | Database config; DB disabled if omitted           |
| `openApi`          | `OpenApiConfig`                              | No       | —       | Swagger/OpenAPI settings                          |
| `logging`          | `LoggingConfig`                              | No       | —       | Logging level, format, exclusions                 |
| `cors`             | `CorsConfig`                                 | No       | —       | CORS origins, methods, credentials                |
| `multiTenancy`     | `MultiTenancyConfig`                         | No       | —       | Multi-tenancy header and enforcement              |
| `requestTimeoutMs` | `number`                                     | No       | `30000` | Request timeout in ms                             |

<details>
<summary>Sub-config interfaces</summary>

**`RedisConfig`**

| Property            | Type     | Default    | Description                     |
| ------------------- | -------- | ---------- | ------------------------------- |
| `url`               | `string` | —          | Redis connection URL (required) |
| `ttlDefaultSeconds` | `number` | `300`      | Default cache TTL               |
| `keyPrefix`         | `string` | `"haykal"` | Cache key prefix                |

**`DatabaseConfig`**

| Property        | Type                | Default      | Description                  |
| --------------- | ------------------- | ------------ | ---------------------------- |
| `host`          | `string`            | —            | Database host (required)     |
| `port`          | `number`            | —            | Database port (required)     |
| `username`      | `string`            | —            | Database user (required)     |
| `password`      | `string`            | —            | Database password (required) |
| `database`      | `string`            | —            | Database name (required)     |
| `type`          | `string`            | `'postgres'` | Database type                |
| `ssl`           | `DatabaseSslConfig` | —            | SSL config                   |
| `poolSize`      | `number`            | `10`         | Connection pool size         |
| `logging`       | `boolean`           | `false`      | TypeORM query logging        |
| `synchronize`   | `boolean`           | `false`      | Auto-sync schema (dev only)  |
| `migrationsRun` | `boolean`           | `false`      | Run migrations on boot       |
| `retryAttempts` | `number`            | `3`          | Connection retry count       |
| `retryDelay`    | `number`            | `3000`       | Retry delay in ms            |

**`OpenApiConfig`**

| Property      | Type      | Default        | Description       |
| ------------- | --------- | -------------- | ----------------- |
| `enabled`     | `boolean` | `false`        | Enable Swagger UI |
| `title`       | `string`  | `'Haykal API'` | API title         |
| `description` | `string`  | —              | API description   |
| `version`     | `string`  | `'1.0.0'`      | API version       |
| `path`        | `string`  | `'/api/docs'`  | Swagger UI path   |

**`LoggingConfig`**

| Property        | Type                                     | Default                           | Description            |
| --------------- | ---------------------------------------- | --------------------------------- | ---------------------- |
| `level`         | `'debug' \| 'info' \| 'warn' \| 'error'` | `'debug'` (dev) / `'info'` (prod) | Log level              |
| `json`          | `boolean`                                | `false` (dev) / `true` (prod)     | JSON format            |
| `excludeRoutes` | `string[]`                               | `['/health', '/ready']`           | Routes to skip logging |

**`CorsConfig`**

| Property      | Type       | Default | Description          |
| ------------- | ---------- | ------- | -------------------- |
| `origins`     | `string[]` | —       | Allowed origins      |
| `methods`     | `string[]` | —       | Allowed HTTP methods |
| `credentials` | `boolean`  | —       | Allow credentials    |

**`MultiTenancyConfig`**

| Property             | Type      | Default         | Description                     |
| -------------------- | --------- | --------------- | ------------------------------- |
| `enabled`            | `boolean` | —               | Enable multi-tenancy (required) |
| `headerName`         | `string`  | `'X-Tenant-ID'` | Tenant header name              |
| `enforceOnAllRoutes` | `boolean` | `false`         | Require tenant on all routes    |

</details>

### Async Configuration

```typescript
CoreBackendModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (config: ConfigService) => ({
    appName: config.get('APP_NAME'),
    env: config.get('NODE_ENV'),
    // ...
  }),
  inject: [ConfigService],
});
```

## Key Exports

| Export                | Type          | Description                                 |
| --------------------- | ------------- | ------------------------------------------- |
| `CoreBackendModule`   | NestJS Module | Global module — import first in `AppModule` |
| `CORE_BACKEND_CONFIG` | Symbol        | Inject the full config object               |
| `REDIS_CLIENT`        | Symbol        | Inject the ioredis client instance          |

### Request Context

| Export                  | Type     | Description                                                                              |
| ----------------------- | -------- | ---------------------------------------------------------------------------------------- |
| `RequestContextService` | Service  | Access current request context (traceId, userId, tenantId, etc.)                         |
| `RequestContext`        | Class    | Context object with `traceId`, `requestId`, `tenantId`, `userId`, `roles`, `permissions` |
| `getRequestContext()`   | Function | Get context from `AsyncLocalStorage` outside DI                                          |

### Caching

| Export            | Type    | Description                                   |
| ----------------- | ------- | --------------------------------------------- |
| `CacheService`    | Service | Redis-backed cache with fail-open behavior    |
| `CacheKeyBuilder` | Class   | Deterministic, tenant-aware cache key builder |

### Errors

| Export              | HTTP Status | Description                      |
| ------------------- | ----------- | -------------------------------- |
| `DomainError`       | —           | Base class for all domain errors |
| `NotFoundError`     | 404         | Resource not found               |
| `UnauthorizedError` | 401         | Authentication required          |
| `ForbiddenError`    | 403         | Insufficient permissions         |
| `ConflictError`     | 409         | Resource conflict                |
| `BusinessRuleError` | 422         | Business rule violation          |

### Domain Events

| Export                  | Type       | Description                                                   |
| ----------------------- | ---------- | ------------------------------------------------------------- |
| `DomainEvent`           | Base class | Extend to create domain events (auto `eventId`, `occurredAt`) |
| `DomainEventBus`        | Service    | `emit()` (sync) and `emitAsync()` (awaits handlers)           |
| `@OnDomainEvent(Event)` | Decorator  | Listen for domain events                                      |

### Database

| Export                    | Type             | Description                                                        |
| ------------------------- | ---------------- | ------------------------------------------------------------------ |
| `HaykalDatabaseModule`    | Module           | TypeORM bootstrapping for PostgreSQL                               |
| `HaykalBaseEntity`        | Entity class     | Base entity with `id`, `createdAt`, `updatedAt`, `deletedAt`, etc. |
| `BaseRepository`          | Repository class | `findByIdOrFail`, `save`, `remove` with transaction support        |
| `TransactionService`      | Service          | `runInTransaction()` and `withTransaction()`                       |
| `SnakeCaseNamingStrategy` | Strategy         | camelCase → snake_case column mapping                              |

### Query Building & Filtering

| Export                     | Type     | Description                                                                      |
| -------------------------- | -------- | -------------------------------------------------------------------------------- |
| `QueryOptionsDto`          | DTO      | Combined filter, sort, pagination options                                        |
| `buildFindOptions()`       | Function | Convert `QueryOptionsDto` to TypeORM `FindManyOptions`                           |
| `applyToQueryBuilder()`    | Function | Apply filters to a `SelectQueryBuilder`                                          |
| `buildPaginatedResponse()` | Function | Create paginated response with metadata                                          |
| `FilterOperator`           | Enum     | `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `between`, `is_null`, `ilike`, etc. |

### Decorators

| Export                      | Type      | Description                                 |
| --------------------------- | --------- | ------------------------------------------- |
| `@RawResponse()`            | Decorator | Bypass response envelope wrapping           |
| `@ApiStandardResponse(dto)` | Decorator | Document standard `{ data, meta }` response |
| `@ApiProblemResponse()`     | Decorator | Document RFC 7807 error response            |
| `@RequestTimeout(ms)`       | Decorator | Override request timeout for a route        |

### Guards

| Export             | Type      | Description                       |
| ------------------ | --------- | --------------------------------- |
| `TenantGuard`      | Guard     | Enforce tenant presence on routes |
| `@RequireTenant()` | Decorator | Mark route as requiring a tenant  |

### Logging & OpenAPI

| Export           | Type     | Description                                             |
| ---------------- | -------- | ------------------------------------------------------- |
| `LoggerService`  | Service  | Structured, context-aware logger                        |
| `setupOpenApi()` | Function | Bootstrap Swagger UI with JWT + Tenant security schemes |

## Usage Examples

### Throwing Domain Errors

```typescript
import { NotFoundError, ConflictError } from '@haykal/core-backend';

// In a service
const user = await this.repository.findById(id);
if (!user) {
  throw new NotFoundError('User', id);
}
```

### Using the Cache

```typescript
import { CacheService } from '@haykal/core-backend';

@Injectable()
export class MyService {
  constructor(private readonly cache: CacheService) {}

  async getData(id: string) {
    return this.cache.getOrSet(
      `my-domain:item:${id}`,
      () => this.repository.findById(id),
      { ttlSeconds: 600 },
    );
  }
}
```

### Emitting Domain Events

```typescript
import { DomainEvent, DomainEventBus, OnDomainEvent } from '@haykal/core-backend';

export class OrderCreatedEvent extends DomainEvent {
  constructor(public readonly orderId: string) {
    super();
  }
}

// Emitting
this.domainEventBus.emit(new OrderCreatedEvent(order.id));

// Listening
@OnDomainEvent(OrderCreatedEvent)
async handleOrderCreated(event: OrderCreatedEvent) {
  // React to the event
}
```

### Using Query Building

```typescript
import { QueryOptionsDto, buildFindOptions, buildPaginatedResponse } from '@haykal/core-backend';

async findAll(query: QueryOptionsDto) {
  const options = buildFindOptions(query, {
    allowedFilters: ['status', 'createdAt'],
    allowedSorts: ['createdAt', 'name'],
  });
  const [items, total] = await this.repository.findAndCount(options);
  return buildPaginatedResponse(items, total, query);
}
```

## Architecture

`CoreBackendModule` is `@Global()`. When imported via `forRoot()`, it:

1. Registers global providers: `RequestContextService`, `LoggerService`, `DomainEventBus`, `CacheService`
2. Sets up global `ResponseInterceptor` — auto-wraps responses in `{ data, meta, traceId, requestId }`
3. Sets up global `HttpExceptionFilter` + `ValidationExceptionFilter` — RFC 7807 error responses
4. Configures middleware pipeline: `RequestContextMiddleware` → `TenantResolutionMiddleware` → `RequestLoggerMiddleware` → `TimeoutMiddleware`
5. Conditionally imports `HaykalDatabaseModule` (if `database` config provided)
6. Conditionally enables Redis caching (if `redis` config provided; otherwise no-op)

## Related Packages

- `@haykal/shared` — Shared types and constants
- `@haykal/core-client` — Client-side HTTP client and React Query provider
- [Architecture docs](../../../docs/architecture.md)
- [Backend style guide](../../../docs/backend-styleguide.md)