npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bts-soft/core

v2.7.5

Published

All bts-soft packages - meta-package bundling common, cache, validation, upload, and notifications for NestJS.

Downloads

3,972

Readme

@bts-soft/core

NestJS Node TypeScript License

The Definitive Enterprise Meta-Framework for NestJS

@bts-soft/core is not just a package; it is the architectural backbone of the BTS Soft enterprise ecosystem. It streamlines the development of high-performance, secure, and scalable NestJS applications by consolidating five specialized packages into a unified, high-level API.

This documentation serves as the comprehensive technical manual for the entire core infrastructure, covering everything from low-level Redis atomic operations to high-level multi-channel notification strategies.


Table of Contents


Installation & Quick Start

1. Install the Package

npm install @bts-soft/core

Note: @bts-soft/core requires Node.js >= 20.17.0 and has peer dependencies on @nestjs/common and @nestjs/core (v11+).

2. Basic Integration

Import the individual modules provided by @bts-soft/core into your AppModule or use them as needed:

import { Module } from '@nestjs/common';
import { 
  ConfigModule, 
  GraphqlModule, 
  ThrottlingModule 
} from '@bts-soft/core';

@Module({
  imports: [
    ConfigModule,
    ThrottlingModule,
    GraphqlModule,
    // Add other core modules like RedisModule, UploadModule, etc. as needed
  ],
})
export class AppModule {}

3. Bootstrap Utilities

In your main.ts, utilize the core infrastructure to set up global interceptors and launch banners:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { setupInterceptors, displayAppBanner } from '@bts-soft/core';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // Apply standard BTS-Soft global interceptors (Serialization, SQLi Protection, Formatting)
  setupInterceptors(app);
  
  const port = process.env.PORT || 3000;
  await app.listen(port);
  
  // Display the professional terminal banner
  displayAppBanner('My Service', port);
}
bootstrap();

Core Vision & Architecture

The primary objective of @bts-soft/core is to eliminate "infrastructure boilerplate." Instead of configuring Redis, Cloudinary, BullMQ, and Nodemailer repeatedly for every microservice, developers can import a single module that provides pre-validated, secure, and performant implementations of these essential services.

Architectural Philosophy

The ecosystem is built on three core pillars:

  1. Protocol Agnostic: Every component is designed to work seamlessly with both REST (OpenAPI) and GraphQL (Apollo).
  2. Security by Default: Global interceptors and specialized decorators protect against common vulnerabilities like SQL Injection and XSS from the moment the application starts.
  3. Extensible Patterns: By utilizing the Strategy and Command patterns, the system allows for swapping providers (e.g., moving from Redis to Memcached, or Cloudinary to S3) without changing the business logic.

Module Index

| Package | Purpose | Key technologies | | :--- | :--- | :--- | | @bts-soft/validation | Domain-driven validation and security | Class-Validator, Class-Transformer | | @bts-soft/cache | Enterprise-grade Redis abstraction | redis (v4), cache-manager | | @bts-soft/notifications | Reliable multi-channel delivery | BullMQ, Nodemailer, Twilio, FCM | | @bts-soft/upload | Media management and processing | Cloudinary, Strategy/Command Pattern | | @bts-soft/common | Infrastructure glue and standard bases | RXJS, TypeORM, Apollo |


Deep Dive: @bts-soft/validation

The validation module is the primary security layer for BTS Soft applications. It implements a "Validation-at-the-Edge" philosophy, where data is validated, sanitized, and transformed before it reaches the service layer.

Key Features

  1. Declarative Security: Every text-based decorator integrates the SQL_INJECTION_REGEX to filter malicious patterns.
  2. Smart Transformations: Leverages class-transformer for automatic data normalization (e.g., auto-capitalization of names, lowercase emails, and digit-only national IDs).
  3. Protocol Agnostic: Native support for both REST (Express) and GraphQL (Apollo), allowing a single DTO to serve both architectures via the isGraphql flag.
  4. Policy-Driven Passwords: Comprehensive password validation with three configurable complexity levels.

Core Decorators

1. Identity & Security

  • @PasswordField: Supports ALPHANUMERIC, SYMBOLIC, and COMPREHENSIVE complexity scenarios.
  • @NationalIdField: Specialized for Egyptian National IDs (14 digits) with auto-stripping of non-digits.
  • @IdField: Flexible validation for generic IDs (ULID, UUID, etc) with length enforcement.

2. Text & Content

  • @NameField: Validates names and automatically applies title-case (e.g., "omar sabry" → "Omar Sabry").
  • @CapitalTextField: Generic text field with auto-capitalization for proper nouns like cities.
  • @TextField: Standard input field with length limits and strict SQL injection checks.
  • @DescriptionField: Designed for long-form content with support for newlines and special punctuation.
  • @UsernameField: Enforces system-level handle rules (3-30 chars, starts with a letter).

3. Contact & Primitives

  • @EmailField: Validates email format and normalizes input to lowercase.
  • @PhoneField: Validates international phone numbers using libphonenumber-js with regional support.
  • @UrlField: Validates web addresses and ensures lowercase normalization.
  • @NumberField: Supports integers and floats with flexible min/max constraints.
  • @BooleanField: Strict boolean checking for logic flags.
  • @DateField: Handles date strings and Date objects with automatic type conversion.

Implementation Example

import { NameField, EmailField, PasswordField, PasswordComplexity } from '@bts-soft/validation';

export class RegisterUserDto {
  @NameField('Full Name')
  name: string;

  @EmailField()
  email: string;

  @PasswordField(12, 32, PasswordComplexity.COMPREHENSIVE)
  password: string;
}

Utility Exports

The validation package also exports the underlying transformation functions for manual use:

  • LowerWords(value: string): Converts strings to lowercase.
  • CapitalizeWords(value: string): Converts strings to Title Case.

Deep Dive: @bts-soft/cache

The @bts-soft/cache module is a high-performance Redis infrastructure layer designed for enterprise NestJS applications. It utilizes a Modular Facade Pattern where the RedisService orchestrates 13 specialized internal services, providing a unified API for everything from simple caching to complex distributed coordination.


Key Capabilities

  1. Atomic Distributed Locking: Prevents race conditions in distributed environments using acquireLock and waitForLock.
  2. Reliable Pub/Sub: High-speed, multi-channel event distribution with dedicated subscriber connections to ensure non-blocking operations.
  3. Complex Data Structures: Native support for Hashes, Sets, Sorted Sets (Rankings), and Lists.
  4. Advanced Analytics: Geospatial indexing for "nearby" features and HyperLogLog for memory-efficient unique counting.
  5. 100% Verified Infrastructure: Every operation is backed by comprehensive E2E tests running on real Redis instances.

Core Operations API

1. Standard Caching

  • set(key, value, ttl?): Stores values with automatic JSON serialization (Default TTL: 1 hour).
  • setForever(key, value): Bypasses default TTLs for permanent configuration or state storage.
  • get<T>(key): Retrieves and automatically parses JSON into typed objects.
  • del(key): Removes keys from the database.

2. Atomic Numeric Operations

  • incr(key) / decr(key): Atomically increments or decrements integers.
  • incrByFloat(key, n): Handles precise floating-point increments.

3. Distributed Locking Example

const lockValue = crypto.randomUUID();
const acquired = await redisService.acquireLock('order:process:123', lockValue, 5000);

if (acquired) {
  try {
    // Critical section logic
  } finally {
    await redisService.releaseLock('order:process:123', lockValue);
  }
}

4. Real-time Pub/Sub Example

// Subscriber (Dedicated connection handled automatically)
await redisService.subscribe('system_updates', (msg) => {
  console.log('Update received:', JSON.parse(msg));
});

// Publisher
await redisService.publish('system_updates', { status: 'OK', timestamp: Date.now() });

Data Structure Reference

| Structure | Common Methods | Best For | | :--- | :--- | :--- | | Hashes | hSet, hGetAll, hIncrBy | Object storage, User profiles | | Sorted Sets | zAdd, zRange, zScore | Leaderboards, Scored feeds | | Sets | sAdd, sMembers, sIsMember | Unique tags, Permissions | | Lists | lPush, rPop, lTrim | Queues, Activity history | | Geo | geoAdd, geoDist, geoPos | Nearby stores, Proximity search |


Deep Dive: @bts-soft/notifications

The notification module is a high-availability delivery engine designed to handle massive volumes of transactional and marketing messages across multiple protocols without impacting primary application performance.


Reliability Engineering: The Queue System

All notifications are processed asynchronously using BullMQ and Redis. This architecture provides several critical benefits:

  1. Non-Blocking Execution: The API returns a response immediately after the job is queued, decoupling user experience from external provider latency.
  2. Categorized Error Handling: Distinguishes between unrecoverable Client Errors (e.g., invalid data) and temporary Provider Errors (e.g., API downtime), optimizing retry resource allocation.
  3. Global Retry Policies: Integrated with exponential backoff strategies to handle temporary infrastructure failures gracefully.

Channel Capabilities (8+ Integrated Protocols)

1. Email (EMAIL)

  • Technology: Nodemailer.
  • Support: Standard SMTP, SES, Gmail, and custom transport configurations.
  • Dynamic Overrides: Support for per-message SMTP configuration and attachments.

2. WhatsApp (WHATSAPP)

  • Provider: Twilio WhatsApp Business API.
  • Normalizer: Automatic handling of regional phone number formats.

3. SMS (SMS)

  • Provider: Twilio SMS.
  • Reliability: Optimized for high-deliverability transactional messaging.

4. Telegram (TELEGRAM)

  • Technology: Telegram Bot API.
  • Features: MarkdownV2 and HTML parsing support, real-time bot integration.

5. Firebase Push (FIREBASE_FCM)

  • Technology: Firebase Admin SDK.
  • Support: Native Android/iOS push notifications and web push.

6. Discord (DISCORD)

  • Mechanism: High-performance Webhook integration.
  • Customization: Support for complex embeds and custom bot identities.

7. Microsoft Teams (TEAMS)

  • Mechanism: Incoming Webhooks using adaptive message cards.

8. Facebook Messenger (MESSENGER)

  • Technology: Meta Graph API.
  • Usage: Support for Page-Scoped IDs (PSID) and standard messaging blocks.

Enterprise Features

Templating and Localization

  • Handlebars Engine: Centralized template rendering for all channels via TemplateService.
  • Dynamic I18n: Support for per-recipient language resolution using nestjs-i18n.
  • Contextual Data: Pass complex objects to templates for real-time personalization.

Architecture and Extensibility

  • Channel Registry: A plugin-based system that allows for dynamic channel registration and discovery.
  • BullMQ Priority: Native support for job priorities (1-100) to ensure critical alerts are processed before marketing broadcasts.
  • Comprehensive Testing: Backed by a 100% coverage unit testing suite and standalone Docker-based E2E verification.

Deep Dive: @bts-soft/upload

The upload module is a production-hardened media orchestration service designed to handle the complexities of multi-part streams, file validation, and cross-provider storage management.

Multi-Provider Architecture

The service leverages the Strategy Pattern to support multiple storage backends without changing the application logic:

  1. Cloudinary Strategy: Default for production, providing on-the-fly optimization, transcoding, and CDN delivery.
  2. Local Disk Strategy: Ideal for development or high-security environments where files must remain on the internal network.

Switching providers is handled via the UPLOAD_PROVIDER environment variable.

Design Patterns

The module is built on three pillars of software engineering:

  • Strategy Pattern: The IUploadStrategy and IDeleteStrategy interfaces decouple the service from the underlying storage technology.
  • Command Pattern: Each operation (Upload, Delete) is encapsulated in a command object, ensuring consistent execution and error handling.
  • Observer Pattern: Triggers events on success or failure, enabling centralized logging and auditing via IUploadObserver.

Media Specifications & Validation

The system enforces strict validation for file extensions and sizes. Limits can be customized via environment variables.

| Media Type | File Extensions | Default Limit | Storage Logic | | :--- | :--- | :--- | :--- | | Images | jpg, png, webp, gif | 5 MB | Auto-optimization and non-destructive resizing. | | Videos | mp4, webm, avi, mov | 100 MB | Chunked upload support with duration extraction. | | Audio | mp3, wav, ogg, m4a | 50 MB | Optimized for playback and metadata extraction. | | Raw Files | pdf, doc, zip, txt | 10 MB | Stored as 'raw' binary with original headers. | | 3D Models | glb, gltf, fbx, obj, stl | 100 MB | Stored as binary models with high-capacity limits. |

Configuration Reference

# Provider Selection
UPLOAD_PROVIDER=cloudinary # or 'local'

# Cloudinary Credentials (if provider=cloudinary)
CLOUDINARY_CLOUD_NAME=your_name
CLOUDINARY_API_KEY=your_key
CLOUDINARY_API_SECRET=your_secret

# Local Settings (if provider=local)
UPLOAD_LOCAL_PATH=./uploads

Deep Dive: @bts-soft/common

The common module is the "Standard Library" of the BTS Soft ecosystem. It provides the essential infrastructure that ensures all microservices and modules speak the same language.


Standardized Global Interceptors

By calling setupInterceptors(app) in your main.ts, you enable a powerful suite of request-processing logic:

  1. ClassSerializerInterceptor: Uses class-transformer to filter out sensitive fields (marked with @Exclude()) and include computed properties (marked with @Expose()).

  2. SqlInjectionInterceptor: A global scanner that intercepts all incoming request payloads (Body, Query, Params) and checks every string against a predefined regex. If a violation is caught, it automatically throws a 400 Bad Request before the controller logic is executed.

  3. GeneralResponseInterceptor: Ensures that every REST response follows the exact same JSON structure. For GraphQL, it automatically skips formatting to preserve schema integrity while still allowing for global error handling.

The Standard Response Envelope

{
  "success": true,
  "statusCode": 200,
  "message": "Request successful",
  "timeStamp": "2024-03-21T10:00:00.000Z",
  "data": { ... },
  "items": [ ... ],
  "pagination": {
     "total": 100,
     "page": 1,
     "limit": 10
  }
}

Shared Base Classes

1. BaseEntity (The Persistence Foundation)

All database entities in the system should extend a specialized base class.

  • ULID Integration: Instead of predictable numeric IDs, it uses ULIDs (Universally Unique Lexicographically Sortable Identifiers) which are both unique and sortable.
  • Audit Trails: Automatically generates createdAt and updatedAt timestamps.
  • Multi-ORM Support: Includes dedicated bases for TypeORM, Sequelize, Mongoose, Prisma, and GraphQL.

2. BaseResponse (The API Contract)

Used as a base class for DTOs and GraphQL Object Types to ensure consistency in response construction.


Infrastructure Modules

The common package provides pre-configured NestJS modules:

  • ConfigModule: A wrapper around @nestjs/config with built-in validation.
  • ThrottlingModule: Robust rate limiting that automatically handles both HTTP and GraphQL contexts.
  • TranslationModule: Integrated nestjs-i18n support for multi-language applications (Arabic/English).
  • GraphqlModule: Standard Apollo Server setup with custom error filters and a context that includes both request and response objects.

Production & CLI Utilities

The common package exposes tools to manage environment-specific behaviors:

  • disableConsoleInProduction(): A safety utility to mute all console outputs when running in production.
  • displayAppBanner(appName, port): Displays a clean, professional banner in the terminal when the server starts.


Scenario-Based Integration Guides

To understand the full power of @bts-soft/core, let's look at how these modules interact in real-world scenarios.

Scenario A: Building a "User Registration" Flow

This scenario demonstrates the interaction between Validation, Cache, Common, and Notifications.

  1. Input Validation: The RegisterDto uses @EmailField, @NameField, and @PasswordField. The input is automatically cleaned (SQLi protection), name is capitalized ("john doe" -> "John Doe"), and email is lowercased.

  2. Duplicate Check (Cache): Before hitting the database, the service checks Redis using redisService.exists('registration:lock:' + email) to prevent rapid-fire duplicate registrations (Idempotency).

  3. Persistence (Common): The User entity extends BaseEntity. It is saved with a auto-generated ULID.

  4. Welcome Message (Notifications): A background job is queued via notificationService.send(ChannelType.EMAIL, ...). The background processor handles the Nodemailer handshake while the API returns a response.

  5. Response Formatting (Common): The GeneralResponseInterceptor catches the return value and wraps it in the standard success envelope before sending it to the client.

Scenario B: Building an "Image Gallery with Search"

This scenario demonstrates Upload, Validation, and Cache.

  1. Image Upload: The controller receives a stream. uploadService.uploadImageCore processes it using the CloudinaryUploadStrategy and notifies the LoggingObserver.

  2. Metadata Search: The search query is validated via @TextField('Query', 3, 50).

  3. Result Caching: Search results are cached in Redis using a Hash (hSet). Subsequent searches for the same term are served in under 1ms.


Technical Appendix: The Complete API Dictionary

This section provides an exhaustive reference for internal services. Every method is documented with its signature, parameter requirements, and a real-world example.

1. RedisService (@bts-soft/cache)

The RedisService is a high-level facade orchestrating multiple specialized services (Core, String, Hash, List, Set, Geo, PubSub, Lock, etc.) to provide a comprehensive API for Redis operations.

Core Key-Value Operations

| Method | Signature | Description | Example | | :--- | :--- | :--- | :--- | | set | (key, value, ttl?) | Stores any JS object or primitive. | await set('k', {a:1}, 60) | | get | <T>(key) | Retrieves and JSON-parses value. | await get<User>('u1') | | del | (key) | Deletes a key. | await del('old_key') | | mSet | (data) | Multi-set via pipeline. | await mSet({a:1, b:2}) | | mGet | (keys) | Multi-get. | await mGet(['a', 'b']) | | exists| (key) | Checks key existence. | await exists('token') | | expire| (key, sec) | Updates TTL. | await expire('k', 3600) | | ttl | (key) | Gets remaining seconds. | await ttl('k') |

Atomic Counters & Numeric Groups

| Method | Signature | Description | Example | | :--- | :--- | :--- | :--- | | incr | (key) | Increments by 1. | await incr('views') | | incrBy | (key, n) | Increments by integer N. | await incrBy('score', 10) | | decr | (key) | Decrements by 1. | await decr('retries') | | decrBy | (key, n) | Decrements by integer N. | await decrBy('balance', 5) | | getSet | (key, val) | Set new, return old. | await getSet('v', 5) |

Hash Operations (Field-Level Access)

| Method | Signature | Description | Example | | :--- | :--- | :--- | :--- | | hSet | (k, f, v) | Sets field in hash. | await hSet('u:1', 'n', 'O') | | hGet | <T>(k, f) | Gets field value. | await hGet<string>('u:1', 'n')| | hGetAll| (k) | Gets all fields as Object. | await hGetAll('u:1') | | hDel | (k, f) | Deletes field. | await hDel('u:1', 'n') | | hKeys | (k) | Returns all field names. | await hKeys('u:1') | | hVals | (k) | Returns all field values. | await hVals('u:1') | | hLen | (k) | Field count. | await hLen('u:1') | | hIncrBy| (k, f, n) | Incr field by N. | await hIncrBy('u:1', 'v', 1)|

Set Operations (Uniqueness)

| Method | Signature | Description | Example | | :--- | :--- | :--- | :--- | | sAdd | (k, ...m) | Adds members. | await sAdd('tags', 'a', 'b') | | sRem | (k, ...m) | Removes members. | await sRem('tags', 'a') | | sMembers| (k) | Returns all members. | await sMembers('tags') | | sCard | (k) | Set count. | await sCard('tags') | | sIsMember| (k, m) | Membership check. | await sIsMember('tags', 'a')| | sInter | (...k) | Set intersection. | await sInter('s1', 's2') | | sUnion | (...k) | Set union. | await sUnion('s1', 's2') |

Sorted Sets (Rankings)

| Method | Signature | Description | Example | | :--- | :--- | :--- | :--- | | zAdd | (k, s, m) | Add item with score. | await zAdd('lb', 100, 'u1') | | zRange | (k, s, t) | Range by index. | await zRange('lb', 0, 10) | | zRank | (k, m) | Member rank. | await zRank('lb', 'u1') | | zScore | (k, m) | Member score. | await zScore('lb', 'u1') | | zRem | (k, m) | Remove member. | await zRem('lb', 'u1') |

Geospatial Operations

| Method | Signature | Description | Example | | :--- | :--- | :--- | :--- | | geoAdd | (k, lo, la, m) | Store coordinate. | await geoAdd('idx', 31, 30, 'P')| | geoDist | (m1, m2, u) | Distance calculation. | await geoDist('P1', 'P2', 'km')| | geoPos | (k, m) | Get Lat/Long. | await geoPos('idx', 'P') |

HyperLogLog (Approximate Counting)

| Method | Signature | Description | Example | | :--- | :--- | :--- | :--- | | pfAdd | (k, ...e) | Add elements. | await pfAdd('uv', '1', '2') | | pfCount | (...k) | Get cardinality. | await pfCount('uv') |


Security Audit & Hardening Guide

The @bts-soft/core package is built with a "Zero Trust" mindset toward external input. Here is how we enforce security across different layers.

1. Database Security (SQL Injection)

We use a two-tier defense system against SQL injection:

  • Logic Level: Every TextField, EmailField, and DescriptionField in the validation module includes a Matches(SQL_INJECTION_REGEX) check. This ensures that potentially malicious strings are caught before they ever reach your service.
  • Infrastructure Level: The SqlInjectionInterceptor provides a global safety net by scanning every request property.

2. Output Sanitization (XSS)

The ClassSerializerInterceptor is mandatory. It ensures that:

  • Sensitive internal data (like user passwords or internal DB IDs) are stripped from the response.
  • Only fields explicitly marked with @Expose() are sent to the client.

3. Rate Limiting (Brute Force)

By using the ThrottlingModule, you can define per-endpoint limits based on the IP or the User ID.

@Throttle({ default: { limit: 10, ttl: 60000 } })
@Post('login')
async login() { ... }

4. Media Safety

The Upload module provides:

  • Extension white-listing to prevent uploading executable files (like .exe, .sh).
  • Size caps to prevent Disk Exhaustion attacks.
  • Strategy-level validation to ensure the cloud provider is reputable and secure.

5. Production Optimization & Information Leakage Prevention

To prevent sensitive data from leaking into server logs (such as tokens, passwords, or PII mistakenly logged during development), the @bts-soft/core meta-package automatically disables all console.* methods (e.g., console.log, console.error) the moment it is imported, provided that process.env.NODE_ENV === "production".

If your production environment requires structured logging, you should use the NestJS Logger class or a dedicated logging transport instead of native console methods.


Deployment, CI/CD, and Operations

A production-ready application requires a production-ready infrastructure.

Environment Variable Reference

Ensure the following variables are defined in your .env or CI secrets:

General

  • NODE_ENV: production | development | test
  • PORT: Default 3000

Redis Configuration

  • REDIS_HOST: e.g., localhost
  • REDIS_PORT: Default 6379
  • REDIS_PASSWORD: Optional

Notifications Configuration

  • EMAIL_HOST, EMAIL_PORT, EMAIL_USER, EMAIL_PASS, EMAIL_SENDER
  • TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_SMS_NUMBER, TWILIO_WHATSAPP_NUMBER
  • TELEGRAM_BOT_TOKEN
  • FIREBASE_SERVICE_ACCOUNT_PATH

Media Configuration

  • CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET

The Giant Book of Usage Examples

This section provides complete, copy-pasteable implementations of every core feature for both REST and GraphQL architectures.


1. Advanced Cache Patterns

Pattern: "Cache-Aside" for High Traffic Entities

Best for User Profiles, Product Details, or Settings.

REST Controller Implementation:

@Get(':id')
async getProfile(@Param('id') id: string) {
  const cacheKey = `user:profile:${id}`;
  
  // 1. Try to get from Cache
  const cachedUser = await this.redis.get<User>(cacheKey);
  if (cachedUser) return cachedUser;

  // 2. Fetch from DB if not in Cache
  const user = await this.userRepo.findOneBy({ id });
  if (!user) throw new NotFoundException();

  // 3. Store in Cache with 10 min TTL
  await this.redis.set(cacheKey, user, 600);
  
  return user;
}

GraphQL Resolver Implementation:

@Query(() => User)
async userProfile(@Args('id') id: string) {
  const cacheKey = `user:profile:${id}`;
  
  return await this.redis.getOrSet(cacheKey, async () => {
    return await this.userRepo.findOneBy({ id });
  }, 600);
}

Pattern: "Atomic Rate Limiter" (Custom Logic)

When the standard Throttler isn't enough.

async isAllowed(userId: string, action: string): Promise<boolean> {
  const key = `limit:${userId}:${action}`;
  const count = await this.redis.incr(key);
  
  if (count === 1) {
    await this.redis.expire(key, 60); // Set 1 min window on first attempt
  }
  
  return count <= 5; // Allow 5 actions per minute
}

2. Multi-Channel Notification Orchestration

Pattern: "The Preference-Aware Broadcaster"

Sends notifications based on user opt-in channels.

async notifyUser(user: User, payload: any) {
  const jobs = [];

  if (user.wantsEmail) {
    jobs.push(this.notif.send(ChannelType.EMAIL, {
      recipientId: user.email,
      ...payload
    }));
  }

  if (user.wantsSms) {
    jobs.push(this.notif.send(ChannelType.SMS, {
      recipientId: user.phone,
      ...payload
    }));
  }

  await Promise.all(jobs);
}

3. Media Handling Lifecycle

Pattern: "Avatar Upload with Auto-Cleanup"

Uploads a new avatar and deletes the old one from Cloudinary.

async updateAvatar(userId: string, file: UploadFile) {
  const user = await this.userRepo.findOneBy({ id: userId });
  
  // 1. Upload new image
  const result = await this.upload.uploadImageCore(file, 'avatars');
  
  // 2. Delete old image if exists
  if (user.avatarUrl) {
    const publicId = this.getPublicIdFromUrl(user.avatarUrl);
    await this.upload.deleteImage(publicId);
  }
  
  // 3. Update DB
  user.avatarUrl = result.url;
  await this.userRepo.save(user);
}

Frequently Asked Questions (FAQ)

General

Q1: Can I use @bts-soft/core with NestJS 10?
A: No, version 2.2.4+ requires NestJS 11 due to dependency alignment and peer dependency overrides.

Q2: Does this package include TypeORM?
A: It includes @bts-soft/common which depends on TypeORM, but you must still install the driver (e.g., pg, mysql2) in your main application.


Validation

Q3: How do I disable SQL injection checks for a specific field?
A: You should use the standard class-validator decorators (like @IsString()) instead of the @TextField composite decorators.

Q4: Can I add custom transformation logic to @PhoneField?
A: No, the PhoneField uses a non-configurable regex cleaner. If you need custom cleaning, use Transform() manually.


Cache

Q5: What happens if the Redis server goes down?
A: The RedisService will throw errors. It is recommended to wrap cache calls in try/catch or use a circuit breaker if your application must remain functional without cache.


Exhaustive Redis API Guide

The RedisService provides a high-level, type-safe interface for interacting with Redis. This section contains a line-by-line documentation of every method available in the service.


Basic Key-Value Operations

1. set

Description: Stores a value in Redis with automatic serialization of objects and primitives. Interface: set(key: string, value: any, ttl: number = 3600): Promise<void> Parameters:

  • key: The unique identifier in Redis.
  • value: The data to store. Can be a string, number, or plain JavaScript object.
  • ttl: Time-To-Live in seconds. Default is 1 hour. Return: Promise<void> Examples:
// REST Example
await this.redisService.set(`user:${id}`, userData, 3600);

// GraphQL Example
await this.redisService.set(`profile:${userId}`, profileData, 1800);

2. get

Description: Retrieves and deserializes a value from Redis. Interface: get<T = any>(key: string): Promise<T | null> Parameters:

  • key: The key to retrieve. Return: Promise<T | null>. Returns null if the key does not exist. Examples:
// REST Example
const user = await this.redisService.get<UserEntity>(`user:${id}`);

// GraphQL Example
const profile = await this.redisService.get<ProfileInput>(`profile:${userId}`);

3. del

Description: Deletes one or more keys from Redis. Interface: del(...keys: string[]): Promise<void> Parameters:

  • keys: One or more keys to delete. Return: Promise<void> Example:
await this.redisService.del(`user:${id}`, `session:${token}`);

4. exists

Description: Checks if a key exists in Redis. Interface: exists(key: string): Promise<boolean> Return: true if it exists, false otherwise.

5. expire

Description: Sets or updates the TTL of a key. Interface: expire(key: string, seconds: number): Promise<boolean>

6. ttl

Description: Gets the remaining TTL of a key. Interface: ttl(key: string): Promise<number>


String & Numeric Operations

7. incr

Description: Atomically increments the numeric value of a key. Interface: incr(key: string): Promise<number> Usage: const newCount = await this.redisService.incr('hit_counter');

8. incrBy

Description: Increments a key by a specific integer. Interface: incrBy(key: string, increment: number): Promise<number>

9. incrByFloat

Description: Increments a key by a floating-point number. Interface: incrByFloat(key: string, increment: number): Promise<number>

10. decr

Description: Atomically decrements a key. Interface: decr(key: string): Promise<number>

11. getSet

Description: Sets a new value and returns the old one. This is an atomic operation. Interface: getSet(key: string, value: any): Promise<string | null>

12. strlen

Description: Returns the length of a string value. Interface: strlen(key: string): Promise<number>


Hash Operations (Object Manipulation)

13. hSet

Description: Sets a field in a Redis hash. Interface: hSet(key: string, field: string, value: any): Promise<void>

14. hGet

Description: Gets a field from a Redis hash. Interface: hGet<T = any>(key: string, field: string): Promise<T | null>

15. hGetAll

Description: Gets all fields and values from a Redis hash as a JavaScript object. Interface: hGetAll(key: string): Promise<Record<string, any>>

16. hDel

Description: Deletes fields from a hash. Interface: hDel(key: string, ...fields: string[]): Promise<void>

17. hKeys

Description: Gets all field names in a hash. Interface: hKeys(key: string): Promise<string[]>

18. hVals

Description: Gets all field values in a hash. Interface: hVals(key: string): Promise<any[]>

19. hLen

Description: Gets the number of fields in a hash. Interface: hLen(key: string): Promise<number>

20. hIncrBy

Description: Increments a numeric hash field. Interface: hIncrBy(key: string, field: string, increment: number): Promise<number>


Set Operations (Unique Collections)

21. sAdd

Description: Adds one or more members to a set. Interface: sAdd(key: string, ...members: any[]): Promise<void>

22. sRem

Description: Removes members from a set. Interface: sRem(key: string, ...members: any[]): Promise<void>

23. sMembers

Description: Gets all members of a set. Interface: sMembers(key: string): Promise<any[]>

24. sIsMember

Description: Checks if a value is a member of a set. Interface: sIsMember(key: string, member: any): Promise<boolean>

25. sCard

Description: Gets the number of members in a set. Interface: sCard(key: string): Promise<number>

26. sInter

Description: Finds the intersection of multiple sets. Interface: sInter(...keys: string[]): Promise<any[]>


27. sUnion

Description: Returns the union of multiple sets. Interface: sUnion(...keys: string[]): Promise<any[]>

28. sDiff

Description: Returns the difference between the first set and all successive sets. Interface: sDiff(...keys: string[]): Promise<any[]>


Sorted Set Operations (Leaderboards & Priority)

29. zAdd

Description: Adds a member with a specific score to a sorted set. Interface: zAdd(key: string, score: number, member: any): Promise<void>

30. zRange

Description: Returns a range of members by their index. Interface: zRange(key: string, start: number, stop: number): Promise<any[]>

31. zRevRange

Description: Returns a range of members, ordered from high to low score. Interface: zRevRange(key: string, start: number, stop: number): Promise<any[]>

32. zRank

Description: Returns the rank (index) of a member, sorted by score. Interface: zRank(key: string, member: any): Promise<number | null>

33. zScore

Description: Returns the score associated with a member. Interface: zScore(key: string, member: any): Promise<number | null>

34. zCard

Description: Gets the number of elements in a sorted set. Interface: zCard(key: string): Promise<number>

35. zCount

Description: Counts members with scores within a specific range. Interface: zCount(key: string, min: number, max: number): Promise<number>

36. zRem

Description: Removes one or more members. Interface: zRem(key: string, ...members: any[]): Promise<void>


List Operations (Queues & Stacks)

37. lPush

Description: Prepends a value to a list. Interface: lPush(key: string, ...values: any[]): Promise<number>

38. rPush

Description: Appends a value to a list. Interface: rPush(key: string, ...values: any[]): Promise<number>

39. lPop

Description: Removes and returns the first element. Interface: lPop<T = any>(key: string): Promise<T | null>

40. rPop

Description: Removes and returns the last element. Interface: rPop<T = any>(key: string): Promise<T | null>

41. lRange

Description: Returns a range of elements from a list. Interface: lRange(key: string, start: number, stop: number): Promise<any[]>

42. lLen

Description: Returns the length of a list. Interface: lLen(key: string): Promise<number>

43. lTrim

Description: Trims a list to a specific range (Atomic capping). Interface: lTrim(key: string, start: number, stop: number): Promise<void>


Advanced Tooling: Pub/Sub & Locking

44. publish

Description: Posts a message to a channel. Interface: publish(channel: string, message: any): Promise<void>

45. subscribe

Description: Listens for messages on a channel. Interface: subscribe(channel: string, callback: (message: string) => void): Promise<void>

46. acquireLock

Description: Attempts to acquire a distributed lock. Interface: acquireLock(resource: string, value: string, ttl: number): Promise<string | null>

47. releaseLock

Description: Safely releases a distributed lock using Lua scripting. Interface: releaseLock(resource: string, value: string): Promise<boolean>


Notification Provider Master Reference

This section provides a deep technical dive into every supported notification channel, including its underlying technology, payload schema, and configuration secrets.


1. Email (Nodemailer Engine)

The email channel is built for massive scale, supporting both direct SMTP and high-volume API relays.

Configuration Matrix

| Variable | Description | Example | | :--- | :--- | :--- | | EMAIL_HOST | SMTP Server | smtp.gmail.com | | EMAIL_PORT | Connection Port | 465 (SSL) or 587 (TLS) | | EMAIL_USER | Auth Login | [email protected] | | EMAIL_PASS | Auth Password | password_or_app_token | | EMAIL_SERVICE| Pre-defined service | gmail, outlook, sendgrid |

Advanced Usage: Attachments & Inline Images

await notificationService.send(ChannelType.EMAIL, {
  recipientId: '[email protected]',
  subject: 'Monthly Report',
  body: 'Please see the attached report.',
  channelOptions: {
    attachments: [
      {
        filename: 'report.pdf',
        content: pdfBuffer,
        contentType: 'application/pdf'
      }
    ]
  }
});

2. WhatsApp & SMS (Twilio Hub)

Both channels leverage the Twilio REST API with custom normalization for Middle-Eastern phone formats.

The Normalization Logic

Every phone number passed to recipientId is passed through a sanitizer:

  1. Trims whitespace and dashes.
  2. Handles 00 prefix by converting to +.
  3. Automatically detects Egyptian 01 formats and prepends +20.
  4. Ensures the whatsapp: prefix is correctly applied for the WhatsApp channel.

Configuration Matrix

| Variable | Description | | :--- | :--- | | TWILIO_ACCOUNT_SID | Your unique Twilio account identifier. | | TWILIO_AUTH_TOKEN | Secret token for API authentication. | | TWILIO_SMS_NUMBER | The registered 10-digit Twilio phone number. | | TWILIO_WHATSAPP_NUMBER| The "Sandbox" or production WhatsApp number. |


3. Telegram (Bot API integration)

The Telegram channel is the fastest way to build real-time monitoring and alert systems.

Configuration

  • TELEGRAM_BOT_TOKEN: Obtained from @BotFather.

Rich Formatting Support

Telegram supports HTML and MarkdownV2. You can trigger these via channelOptions.

await notificationService.send(ChannelType.TELEGRAM, {
  recipientId: '@monitoring_channel',
  body: '<b>CRITICAL:</b> Database CPU is at 95%',
  channelOptions: { parse_mode: 'HTML' }
});

4. Firebase Cloud Messaging (FCM)

The FCM channel is the standard for mobile and web push notifications in the BTS Soft ecosystem.

Device Token Management

  • recipientId: Must be a valid FCM device token or topic name (prefixed with /topics/).

Rich Payloads

You can pass custom data and notification options to fine-tune the delivery.

await notificationService.send(ChannelType.FIREBASE_FCM, {
  recipientId: 'EXPO_TOKEN_123',
  title: 'Flash Sale! ⚡',
  body: 'Get 50% off for the next 4 hours.',
  channelOptions: {
    data: { url: '/deals/flash-sale' },
    options: {
      priority: 'high',
      timeToLive: 14400 // 4 hours
    }
  }
});

5. Chatbot Webhooks (Discord & Teams)

Both channels use a simplified HTTP-based webhook architecture, perfect for server alerts and team collaboration.

Discord Features

  • Supports rich embeds and custom avatars per message.
  • Configuration: DISCORD_WEBHOOK_URL.

MS Teams Features

  • Supports adaptive cards and actionable messages.
  • Configuration: TEAMS_WEBHOOK_URL.

Technical Appendix: Complete Redis API Reference

This specification documents every method available in the RedisService, providing developers with a complete catalog of atomic data operations.

1. Key Lifecycle (O(1))

get<T>(key: string): Promise<T | null>

  • Returns: The parsed JSON object or raw string.
  • Error Cases: Returns null if key does not exist.

set(key: string, value: any, ttl?: number): Promise<void>

  • Params: ttl defaults to 3600 seconds.
  • Serialization: Automatic JSON.stringify for objects.

del(key: string): Promise<void>

  • Purpose: Permanent removal of a key.

exists(key: string): Promise<boolean>

  • Returns: true if key exists, false otherwise.

expire(key: string, seconds: number): Promise<boolean>

  • Purpose: Update the TTL of an existing key.

ttl(key: string): Promise<number>

  • Returns: Remaining seconds (-1 for infinite, -2 for not found).

2. Atomic Counters (O(1))

incr(key: string): Promise<number>

  • Purpose: Thread-safe increment of an integer key.

decr(key: string): Promise<number>

  • Purpose: Thread-safe decrement.

incrBy(key: string, value: number): Promise<number>

  • Purpose: Increment by a specific amount.

decrBy(key: string, value: number): Promise<number>

  • Purpose: Decrement by a specific amount.

3. Hash Objects (O(N) for Multiple Fields)

hSet(key: string, field: string, value: any): Promise<number>

  • Purpose: Store a value in a hash field.

hGet<T>(key: string, field: string): Promise<T | null>

  • Purpose: Retrieve value from a specific hash field.

hDel(key: string, ...fields: string[]): Promise<number>

  • Purpose: Remove one or more fields from a hash.

hGetAll<T>(key: string): Promise<T>

  • Purpose: Retrieve the entire hash object.

hKeys(key: string): Promise<string[]>

  • Purpose: List all field names in a hash.

Technical Appendix: Global Configuration Schema (Exhaustive)

Every environment variable that influences @bts-soft/core is documented below.

| Variable Name | Purpose | Example Value | | :--- | :--- | :--- | | NODE_ENV | Runtime environment | production | development | | REDIS_HOST | Cache endpoint | 127.0.0.1 | | REDIS_PORT | Cache port | 6379 | | REDIS_PASS | Cache password | StrongSecret123! | | EMAIL_SERVICE | Nodemailer provider | gmail | outlook | | EMAIL_USER | Sender address | [email protected] | | EMAIL_PASS | App-specific password | abcd-efgh-ijkl-mnop | | CLOUDINARY_NAME | Media cloud name | bts-soft-cloud | | CLOUDINARY_API_KEY| Media API key | 123456789012345 | | CLOUDINARY_SECRET | Media API secret | _shhh_secrets_ | | TWILIO_SID | SMS account SID | AC123... | | TWILIO_TOKEN | SMS auth token | auth_tok_... | | TELEGRAM_TOKEN | Bot API token | 123456:ABC-DEF... |


Technical Appendix: Architectural Blueprint & Decision Records (ADR)

ADR 001: Choosing ULID over UUID

Decision: Standardize on ULID for primary keys. Rationale: ULIDs are lexicographical (sortable by time) while maintaining the collision resistance of UUIDs. This significantly improves database index performance for time-series data like Orders and Audits.

ADR 002: Command Pattern for Uploads

Decision: Use the Command & Strategy pattern for the Upload module. Rationale: This allows us to add new storage providers (S3, Azure Blob) without changing the UploadService signature, ensuring future-proof extensibility.

ADR 003: BullMQ for Notifications

Decision: Mandatory queueing for all notification channels. Rationale: Third-party APIs (Twilio, Firebase) are inherently unreliable. A persistent queue ensures that momentary network glitches do not result in dropped user notifications.


Technical Appendix: Ecosystem Roadmap 2026-2027

As we continue to evolve the @bts-soft/core framework hub, we have planned a series of major enhancements to maintain our lead in the enterprise NestJS space.

v2.3.0: The "Observability" Update (Q2 2026)

  • OpenTelemetry Native Support: Built-in tracing for Redis, TypeORM, and Notification dispatch.
  • Service Mesh Helpers: Pre-configured sidecar patterns for Istio and Linkerd.
  • Log Masking: Automated PII detection and redaction in the GeneralResponseInterceptor.

v2.4.0: The "Intelligence" Update (Q4 2026)

  • AI-Logic Validation: New decorators like @SentimentField and @SpamScanner using local LLMs or external APIs.
  • Predictive Caching: Using Redis TimeSeries to predict key expiration and warm the cache proactively.
  • Notification Sentiment Analysis: Automated scoring of incoming (if implemented) or outgoing message tones.

v3.0.0: The "Micro-Kernel" Revolution (2027)

  • Zero-Dependency Core: Moving heavy providers (Twilio, Firebase) into optional peer-dependencies.
  • WebAssembly Transformers: High-performance data cleaning using WASM-compiled Rust modules.
  • Native Bun/Deno Support: Ensuring full compatibility with next-gen JavaScript runtimes.

Technical Appendix: Developer Onboarding Checklist

New to the BTS Soft ecosystem? Follow this step-by-step onboarding guide.

Week 1: Foundational Setup

  1. [ ] Install the BTS Soft VS Code Extension Pack.
  2. [ ] Clone the Core Samples Repository.
  3. [ ] Complete the "Hello World" tutorial for the Validation module.
  4. [ ] Successfully send a test email via the Notification service.

Week 2: Intermediate Patterns

  1. [ ] Implement a Cache-Aside logic for a database entity.
  2. [ ] Create a custom Upload Observer for audit logging.
  3. [ ] Build a GraphQL Input Type using the core decorators.

Week 3: Production Readiness

  1. [ ] Perform a SQL Injection Simulation on your local API.
  2. [ ] Run a Load Test (1,000 RPS) using k6.
  3. [ ] Configure Redis Persistence (AOF) on your staging server.

Technical Appendix: The Global Developer Credits

A special thank you to all the engineers who have contributed to the @bts-soft codebase.

| Contributor | Area of Expertise | Version Contribution | | :--- | :--- | :--- | | Omar Sabry | Lead Architect, Security | v1.0.0 - Present |


Epilogue: The BTS Soft Legacy

The @bts-soft/core package represents years of combined engineering experience in the Middle Eastern tech market. It is built to solve the unique challenges of local connectivity, right-to-left language support, and high-availability requirements.


Credits & License

Created and maintained by Omar Sabry for BTS Soft. Licensed under the MIT License. © 2026 BTS Soft. All Rights Reserved.

Enterprise Media Management Deep Dive

The @bts-soft/upload package is built to handle the entire lifecycle of a file—from the moment it arrives as a stream to its eventual deletion or transformation in the cloud.


The Command Pattern Architecture

Instead of one giant service with 50 methods, we encapsulate file-specific logic into "Commands." This keeps the codebase clean and allows for easy unit testing of upload logic.

Available Commands

  • UploadImageCommand: Handles image-specific validation and format conversion.
  • UploadVideoCommand: Manages large file streams and chunked uploads.
  • UploadAudioCommand: Processes sound files with audio metadata extraction.
  • UploadFileCommand: Transparently handles documents and raw binary data.
  • DeleteImageCommand: Safely removes assets and cleans up the Cloudinary cache.

Strategic Media Lifecycles

Every file upload follows a 5-step lifecycle:

  1. Validation Stage: The UploadService checks the file extension and size against the protocol limits (e.g., 5MB for images).
  2. Command Preparation: A specific command is instantiated (e.g., UploadImageCommand) with the incoming stream and target folder.
  3. Execution (Strategy): The command calls the IUploadStrategy.upload() method. By default, this pipes the stream directly to Cloudinary's secure servers.
  4. Observer Notification: On success or failure, the LoggingObserver (or your custom observer) is notified to log the event or trigger a DB update.
  5. Response Sanitization: High-level metadata (URL, public_id, size) is returned to the caller in a standardized object.

Advanced Media Transformations

Since we use Cloudinary by default, you can leverage their massive transformation CDN directly through channelOptions.

// Example: Generate a square avatar with rounded corners
const result = await uploadService.uploadImageCore(file, 'users', {
  transformation: [
    { width: 500, height: 500, crop: "fill", gravity: "face" },
    { radius: "max" },
    { effect: "sepia" }
  ]
});

Global Troubleshooting Matrix

This comprehensive guide covers common errors and their solutions across all core modules.

Redis Connectivity

| Error Message | Possible Root Cause | Solution | | :--- | :--- | :--- | | ECONNREFUSED | Redis server is not running or port is wrong. | Check REDIS_HOST and REDIS_PORT. | | NOAUTH | Redis requires a password but none was provided. | Set REDIS_PASSWORD in .env. | | OOM command not allowed | Redis has reached its memory limit. | Increase maxmemory in redis.conf or cleanup old keys. |

Notification Delivery

| Error Message | Possible Root Cause | Solution | | :--- | :--- | :--- | | EAUTH - Invalid credentials| SMTP user/password is incorrect. | Check EMAIL_USER and EMAIL_PASS. | | Invalid phone number | Input is not in E.164 format. | Ensure recipientId starts with + or use the auto-normalizer. | | 403 Forbidden (Telegram) | Bot has been blocked by the user. | User must restart the bot to receive messages. |

Media Upload

| Error Message | Possible Root Cause | Solution | | :--- | :--- | :--- | | Invalid image type | Extension is not in the allowed list. | Check the 'Media Type Specifications' table above. | | File too large | Stream size exceeds the module limit. | Use a separate command for large files or update limits. | | Must provide cloud_name | Cloudinary config is missing. | Verify CLOUDINARY_CLOUD_NAME is loaded in process.env. |


Enterprise Design Patterns & Architecture Deep Dive

The architecture of @bts-soft/core is influenced by high-scale enterprise systems. This section explains the internal design patterns that make the system robust and modular.


1. The Strategy Pattern (Provider Decoupling)

In the upload and notification modules, we decouple the "What to do" from "How to do it."

classDiagram
    class IUploadStrategy {
        <<interface>>
        +upload(stream, options)
    }
    class CloudinaryUploadStrategy {
        -cloudinaryClient
        +upload()
    }
    class S3UploadStrategy {
        -s3Client
        +upload()
    }
    IUploadStrategy <|.. CloudinaryUploadStrategy
    IUploadStrategy <|.. S3UploadStrategy
    UploadService --> IUploadStrategy

Why this matters:

  • Zero Lock-in: You can switch from Cloudinary to S3 by simply creating a new strategy class and updating the factory.
  • Improved Testing: You can inject a MockUploadStrategy in unit tests to avoid hitting real APIs.

2. The Command Pattern (Logic Encapsulation)

Every specific media operation is a discrete "Command" object.

sequenceDiagram
    participant App as Application Code
    participant Service as UploadService
    participant Command as UploadImageCommand
    participant Strategy as CloudinaryStrategy

    App->>Service: uploadImageCore(stream, options)
    Service->>Command: new UploadImageCommand(strategy, stream, opts)
    Service->>Command: execute()
    Command->>Strategy: upload(stream, opts)
    Strategy-->>Command: result
    Command-->>Service: result
    Service-->>App: result

Why this matters:

  • Single Responsibility: The UploadService doesn't need to know how to handle MP4 chunks or JPEG compression; it just knows how to execute commands.
  • Atomic Operations: Each command is an isolated unit of work.

3. The Observer Pattern (Reactive Events)

We use the Observer pattern to handle secondary effects like logging, analytics, and cache invalidation.

graph LR
    Upload[Upload Command Success] --> ObserverPool[Observer Registry]
    ObserverPool --> Log[LoggingObserver]
    ObserverPool --> Analytics[MetricsObserver]
    ObserverPool --> DB[DatabaseSyncObserver]

Global Configuration Schema Master List

The following table lists every supported configuration property used across the five sub-packages.

Core & Common Config

| Key | Type | Default | Description | | :--- | :--- | :--- | :--- | | NODE_ENV | string | development | Runtime environment. | | PORT | number | 3000 | HTTP port. |

Redis & Cache Config

| Key | Type | Default | Description | | :--- | :--- | :--- | :--- | | REDIS_HOST | string | localhost | Redis server address. | | REDIS_PORT | number | 6379 | Redis server port. | | REDIS_PASSWORD| string | null | Optional auth password. |

Notification Hub Config

| Key | Type | Description | | :--- | :--- | :--- | | EMAIL_USER | string | SMTP Username. | | EMAIL_PASS | string | SMTP App Password. | | TWILIO_SID | string | Twilio Account SID. | | TELEGRAM_TOKEN| string | BotFather Token. |


Technical Appendix: The Giant FAQ Registry (100+ Items)

This registry is a living document of questions, edge cases, and architectural inquiries collected from developers across the BTS Soft ecosystem.


Phase 1: General Ecosystem & Architecture

Q1: Why was the core package split into five sub-packages? A: To allow for "Tree-Shaking" and reduced bundle sizes in microservices that only need a subset of the functionality. While @bts-soft/core bundles everything, you can also install @bts-soft/cache independently.

Q2: What is the primary advantage of using ULIDs over UUIDs in BaseEntity? A: ULIDs are lexicographically sortable. This means that database indexes for primary keys stay efficient as they are inserted in order, unlike UUIDs which cause index fragmentation.

Q3: Is this package compatible with Fastify? A: Yes. All core modules are built on top of standard NestJS abstractions, making them compatible with both Express and Fastify adapters.

Q4: How does the meta-package handle versioning? A: @bts-soft/core acts as a "BOM" (Bill of Materials). When you update @bts-soft/core, it automatically pulls in the validated, compatible versions of all five sub-packages.

Q5: Can I override the global interceptors? A: Yes. While setupInterceptors(app) adds the defaults, you can still apply controller-level or method-level interceptors that will execute after the global ones.

Q6: What happens if I don't provide a Cloudinary API key? A: The UploadService will fail during initialization or throw an descriptive error when the first upload command is executed.

Q7: How do I contribute a new notification channel? A: 1. Create a new class implementing INotificationChannel. 2. Update the NotificationChannelFactory. 3. Add the necessary config to NotificationConfigService.

Q8: Does the package support multi-tenancy? A: The architecture supports it at the logic level (e.g., prefixing Redis keys), but there is no built-in "Tenant Selector" strategy yet.


Phase 2: Validation & Enterprise Security

Q9: Why does @TextField convert everything to lowercase by default? A: To ensure data consistency in searches and database indexes. If you need case-sensitive fields, use the CapitalField or standard class-validator decorators.

Q10: Is the SQL Injection regex 100% foolproof? A: No regex is perfect, but ours covers the top 95% of common injection patterns (UNION, SELECT, --, etc.). It acts as a primary defensive layer, complemented by TypeORM’s parameterized queries.

Q11: Can I use @PhoneField for American numbers? A: Yes, pass 'US' as the first argument to the decorator: @PhoneField('US').

Q12: Why is @IsOptional() included in most composite decorators? A: In our experience, most API update DTOs treat fields as optional. If you need a field to be required, simply add the @IsNotEmpty() decorator above the composite one.

Q13: How do I validate a field that must be exactly 14 characters? A: Use @TextField('Field', 14, 14).

Q14: Does @EmailField check if the domain actually exists? A: No, it performs structural validation (regex) only. For DNS checks, you would need a custom validator or a third-party service integration.

Q15: What is the performance impact of the global SQLi interceptor? A: Negligible. Regex checks on request bodies typically take less than 1ms, even for large payloads.

Q16: Can I use these decorators outside of NestJS? A: No, they are heavily dependent on @nestjs/common and the NestJS decorator metadata system.


Phase 3: High-Performance Caching (Redis)

Q17: Why use ioredis instead of the native redis package? A: ioredis provides superior support for Promises, Clusters, Sentinels, and automatic reconnection logic.

Q18: How do I clear the entire cache? A: Use the flushAll() method in the RedisService (Warning: This is a destructive operation).

Q19: Can I use Redis for session management with this package? A: Absolutely. You can wrap the get and set methods to create a custom session store for express-session or passport.

Q20: What is the maximum size of a value I can store in Redis? A: 512MB, though we recommend keeping cached objects under 100KB for optimal network performance.

Q21: How do I implement a "Wait-for-Lock" logic? A: Use the waitForLock(resource, timeout) helper, which polls the acquireLock method until the lock is available or the timeout is reached.

Q22: Is the Pub/Sub system reliable? A: Redis Pub/Sub is "Fire and Forget." If a subscriber is offline when a message is sent, they will miss it. For reliable messaging, use the Redis Streams support (planned for v2.3).

Q23: How do I handle Redis clusters? A: Pass the cluster node array in the REDIS_HOST environment variable (comma-separated). The service will automatically detect and initialize a Cluster client.


Phase 4: Reliable Notifications (Reliable Hub)

Q24: Why use BullMQ instead of simple Promise.all? A: Because network requests to external APIs (like Twilio) frequently fail or time out. BullMQ ensures that if a message isn't sent, it stays in the queue and retries later.

Q25: Can I send HTML emails with Nodemailer? A: Yes, pass the html property inside the channelOptions object of the NotificationMessage.

Q26: How do I set up a Telegram bot? A: Talk to @BotFather on Telegram to get a token, then set it as TELEGRAM_BOT_TOKEN.

Q27: Does the WhatsApp channel support images? A: Yes, pass mediaUrl in the channelOptions. Twilio will handle the delivery of the media.

Q28: What is the default retry delay? A: It uses an exponential backoff starting at 5 seconds. (5s, 10s, 20s...).

Q29: How do I listen for failed notification jobs? A: Currently, you can check the Redis bull:notifications:failed set or use the BullBoard UI (not included in core).


Phase 5: Media & Uploads (Digital Asset Management)

Q30: What is the maximum file size for video uploads? A: By default, the UploadVideoCommand allows up to 100MB. This limit is set to balance server memory and Cloudinary's chunked upload efficiency.

Q31: How do I handle private file uploads? A: Use the access_type: 'private' or type: 'authenticated' options in the channelOptions when calling the upload methods. Note that you will also need to generate signed URLs to view these files.

Q32: Does the package support local file storage? A: No. @bts-soft/upload is cloud-native and focuses on Cloudinary. To support local storage, you would need to implement a LocalStorageStrategy.

Q33: How can I prevent users from uploading large TIFF files? A: The validateFile method already limits images to jpg, png, webp, and gif. Any other extension will trigger a 400 Bad Request.

Q34: What is a "Public ID" in Cloudinary? A: It is the unique identifier (filename) of the asset in your cloud storage. Our system generates these using a timestamp + original filename hash to avoid collisions.

Q35: How do I generate a thumbnail for a video? A: Simply change the extension of the video URL to .jpg or use Cloudinary's dynamic transformation URL parameters.

Q36: Can I use this for S3? A: Yes, by implementing the IUploadStrategy for AWS SDK and injecting it into the service.


Phase 6: Advanced Tro