@bts-soft/core 3.1.5 → 3.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,71 +1,108 @@
1
1
  # @bts-soft/core
2
2
 
3
- ![NestJS](https://img.shields.io/badge/NestJS-%3E%3D11.0.0-E0234E?style=flat-square&logo=nestjs)
4
- ![Node](https://img.shields.io/badge/Node-%3E%3D20.17.0-339933?style=flat-square&logo=node.js)
5
- ![TypeScript](https://img.shields.io/badge/TypeScript-5.5-3178C6?style=flat-square&logo=typescript)
6
- ![License](https://img.shields.io/badge/License-MIT-blue?style=flat-square)
3
+ [![npm version](https://img.shields.io/npm/v/@bts-soft/core?style=flat-square&color=CB3837&logo=npm)](https://www.npmjs.com/package/@bts-soft/core)
4
+ [![Node](https://img.shields.io/badge/Node->%3D20.17.0-339933?style=flat-square&logo=node.js)](https://nodejs.org)
5
+ [![NestJS](https://img.shields.io/badge/NestJS->%3D11.0.0-E0234E?style=flat-square&logo=nestjs)](https://nestjs.com)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.5-3178C6?style=flat-square&logo=typescript)](https://www.typescriptlang.org)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](https://opensource.org/licenses/MIT)
7
8
 
8
- ## The Definitive Enterprise Meta-Framework for NestJS
9
+ **Author: [Omar Sabry](https://omarsabry.vercel.app/) — Back-End Engineer**
9
10
 
10
- `@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.
11
+ ---
12
+
13
+ ## Overview
14
+
15
+ `@bts-soft/core` is a meta-package that consolidates the entire BTS Soft infrastructure ecosystem into a single, cohesive installation. Rather than managing five independent packages with their own versioning and peer dependency resolution, `@bts-soft/core` pins all packages at compatible, tested versions and exposes every export through a single entry point.
16
+
17
+ Installing `@bts-soft/core` gives you immediate access to:
11
18
 
12
- 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.
19
+ - [@bts-soft/validation](https://www.npmjs.com/package/@bts-soft/validation) — Composite validation decorators, rate limiters, SQL injection prevention
20
+ - [@bts-soft/cache](https://www.npmjs.com/package/@bts-soft/cache) — Enterprise Redis abstraction layer with 15 specialized domain services
21
+ - [@bts-soft/notifications](https://www.npmjs.com/package/@bts-soft/notifications) — Multi-channel queued notification engine (Email, SMS, Push, Chat)
22
+ - [@bts-soft/upload](https://www.npmjs.com/package/@bts-soft/upload) — Production-grade media orchestration with chunked uploads and CDN integration
23
+ - [@bts-soft/common](https://www.npmjs.com/package/@bts-soft/common) — Standard library: interceptors, base entities, ORM adapters, infrastructure modules
13
24
 
14
25
  ---
15
26
 
16
27
  ## Table of Contents
17
- - [Installation \& Quick Start](#installation--quick-start)
18
- - [Core Vision \& Architecture](#core-vision--architecture)
19
- - [Module Index](#module-index)
20
- - [Deep Dive: @bts-soft/validation](#deep-dive-bts-softvalidation)
21
- - [Deep Dive: @bts-soft/cache](#deep-dive-bts-softcache)
22
- - [Deep Dive: @bts-soft/notifications](#deep-dive-bts-softnotifications)
23
- - [Deep Dive: @bts-soft/upload](#deep-dive-bts-softupload)
24
- - [Deep Dive: @bts-soft/common](#deep-dive-bts-softcommon)
25
- - [Security Audit \& Hardening Guide](#security-audit--hardening-guide)
26
- - [Deployment, CI/CD, and Operations](#deployment-cicd-and-operations)
27
- - [The Giant Book of Usage Examples](#the-giant-book-of-usage-examples)
28
- - [Frequently Asked Questions (FAQ)](#frequently-asked-questions-faq)
29
- - [Exhaustive Redis API Guide](#exhaustive-redis-api-guide)
28
+
29
+ 1. [Why a Meta-Package](#why-a-meta-package)
30
+ 2. [Installation](#installation)
31
+ 3. [System Architecture and Design Principles](#system-architecture-and-design-principles)
32
+ 4. [Package: @bts-soft/validation](#package-bts-softvalidation)
33
+ 5. [Package: @bts-soft/cache](#package-bts-softcache)
34
+ 6. [Package: @bts-soft/notifications](#package-bts-softnotifications)
35
+ 7. [Package: @bts-soft/upload](#package-bts-softupload)
36
+ 8. [Package: @bts-soft/common](#package-bts-softcommon)
37
+ 9. [Cross-Package Integration Scenarios](#cross-package-integration-scenarios)
38
+ 10. [Security Model](#security-model)
39
+ 11. [Environment Variable Reference](#environment-variable-reference)
40
+ 12. [Testing and Verification](#testing-and-verification)
41
+ 13. [FAQ](#faq)
30
42
 
31
43
  ---
32
44
 
33
- ## Installation & Quick Start
45
+ ## Why a Meta-Package
46
+
47
+ In a large NestJS monorepo or a microservice mesh, every service needs the same foundational infrastructure: caching, validation, file uploads, and notification dispatching. Without coordination, teams end up manually aligning package versions, discovering peer-dependency conflicts at runtime, and duplicating module setup code.
48
+
49
+ `@bts-soft/core` solves this by:
50
+
51
+ - Pinning all five sub-packages at versions that have been integration-tested together
52
+ - Re-exporting everything from a single entry point so import paths stay clean
53
+ - Providing a single version bump point when the underlying packages update
34
54
 
35
- ### 1. Install the Package
55
+ Consuming a single package instead of five also makes audit pipelines, SBOM generation, and dependency management significantly simpler.
56
+
57
+ ---
58
+
59
+ ## Installation
36
60
 
37
61
  ```bash
38
62
  npm install @bts-soft/core
39
63
  ```
40
64
 
41
- *Note: `@bts-soft/core` requires `Node.js >= 20.17.0` and has peer dependencies on `@nestjs/common` and `@nestjs/core` (v11+).*
65
+ Peer dependencies required in your host application:
66
+
67
+ ```bash
68
+ npm install @nestjs/common @nestjs/core
69
+ ```
70
+
71
+ Minimum runtime requirements:
42
72
 
43
- ### 2. Basic Integration
73
+ | Requirement | Version |
74
+ | :--- | :--- |
75
+ | Node.js | >= 20.17.0 |
76
+ | npm | >= 10.0.0 |
77
+ | NestJS | >= 11.0.0 |
44
78
 
45
- Import the individual modules provided by `@bts-soft/core` into your `AppModule` or use them as needed:
79
+ ### Quickstart
46
80
 
47
81
  ```typescript
48
82
  import { Module } from '@nestjs/common';
49
- import {
50
- ConfigModule,
51
- GraphqlModule,
52
- ThrottlingModule
83
+ import {
84
+ ConfigModule,
85
+ RedisModule,
86
+ ThrottlingModule,
87
+ GraphqlModule,
88
+ NotificationModule,
89
+ UploadModule,
53
90
  } from '@bts-soft/core';
54
91
 
55
92
  @Module({
56
93
  imports: [
57
94
  ConfigModule,
95
+ RedisModule,
58
96
  ThrottlingModule,
59
- GraphqlModule,
60
- // Add other core modules like RedisModule, UploadModule, etc. as needed
97
+ GraphqlModule.forRoot({ autoSchemaFile: true }),
98
+ NotificationModule,
99
+ UploadModule,
61
100
  ],
62
101
  })
63
102
  export class AppModule {}
64
103
  ```
65
104
 
66
- ### 3. Bootstrap Utilities
67
-
68
- In your `main.ts`, utilize the core infrastructure to set up global interceptors and launch banners:
105
+ In `main.ts`:
69
106
 
70
107
  ```typescript
71
108
  import { NestFactory } from '@nestjs/core';
@@ -74,14 +111,9 @@ import { setupInterceptors, displayAppBanner } from '@bts-soft/core';
74
111
 
75
112
  async function bootstrap() {
76
113
  const app = await NestFactory.create(AppModule);
77
-
78
- // Apply standard BTS-Soft global interceptors (Serialization, SQLi Protection, Formatting)
79
114
  setupInterceptors(app);
80
-
81
115
  const port = process.env.PORT || 3000;
82
116
  await app.listen(port);
83
-
84
- // Display the professional terminal banner
85
117
  displayAppBanner('My Service', port);
86
118
  }
87
119
  bootstrap();
@@ -89,2961 +121,1404 @@ bootstrap();
89
121
 
90
122
  ---
91
123
 
92
- ## Core Vision & Architecture
93
-
94
- 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.
124
+ ## System Architecture and Design Principles
95
125
 
96
- ### Architectural Philosophy
126
+ ### High-Level Component Map
97
127
 
98
- The ecosystem is built on three core pillars:
128
+ ```
129
+ @bts-soft/core (Meta-Package)
130
+ |
131
+ ┌───────────────────┼───────────────────┐
132
+ │ │ │
133
+ @bts-soft/validation @bts-soft/notifications @bts-soft/upload
134
+ Decorators Queued Multi-Channel Chunked Upload
135
+ Rate Limiters Engine CDN Orchestration
136
+ SQLi Guards │ │
137
+ │ └─────────┐ ┌───────────┘
138
+ │ ▼ ▼
139
+ @bts-soft/common @bts-soft/cache
140
+ Interceptors Redis Facade
141
+ ORM Adapters ◄────── 15 Domain Services
142
+ Infrastructure (shared by all packages)
143
+ ```
99
144
 
100
- 1. **Protocol Agnostic**: Every component is designed to work seamlessly with both **REST** (OpenAPI) and **GraphQL** (Apollo).
101
- 2. **Security by Default**: Global interceptors and specialized decorators protect against common vulnerabilities like SQL Injection and XSS from the moment the application starts.
102
- 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.
145
+ ### Request Lifecycle
103
146
 
104
- ---
147
+ ```
148
+ Client ──► CommonThrottlerGuard ──[rate limit exceeded]──► 429 Too Many Requests
149
+ │
150
+ [within threshold]
151
+ │
152
+ ▼
153
+ SqlInjectionInterceptor ──[SQLi pattern found]──► 400 Bad Request
154
+ │
155
+ [payload clean]
156
+ │
157
+ ▼
158
+ IdempotencyInterceptor ──[duplicate key in Redis]──► Cached Response
159
+ │
160
+ [new request]
161
+ │
162
+ ▼
163
+ NestJS ValidationPipe (transform + validate DTO)
164
+ │
165
+ ▼
166
+ Controller / Resolver
167
+ │
168
+ ▼
169
+ GeneralResponseInterceptor (wrap in unified envelope)
170
+ │
171
+ ▼
172
+ Client ◄── { success, statusCode, data, timeStamp }
173
+ ```
105
174
 
106
- ## Module Index
175
+ ### Design Patterns in Use
107
176
 
108
- | Package | Purpose | Key technologies |
177
+ | Pattern | Package | Implementation |
109
178
  | :--- | :--- | :--- |
110
- | `@bts-soft/validation` | Domain-driven validation and security | Class-Validator, Class-Transformer |
111
- | `@bts-soft/cache` | Enterprise-grade Redis abstraction | redis (v4), cache-manager |
112
- | `@bts-soft/notifications` | Reliable multi-channel delivery | BullMQ, Nodemailer, Twilio, FCM |
113
- | `@bts-soft/upload` | Media management and processing | Cloudinary, Strategy/Command Pattern |
114
- | `@bts-soft/common` | Infrastructure glue and standard bases | RXJS, TypeORM, Apollo |
115
-
116
- ---
179
+ | **Facade** | `@bts-soft/cache` | `RedisService` provides a single API surface over 15 internal domain services |
180
+ | **Strategy** | `@bts-soft/upload` | `IUploadStrategy` decouples Local Disk and Cloudinary backends |
181
+ | **Strategy** | `@bts-soft/common` | `IdGenerator` swaps between ULID, UUID, Snowflake, and CUID2 at runtime |
182
+ | **Command** | `@bts-soft/upload` | Each media type (image, video, audio, raw) is an encapsulated command object |
183
+ | **Observer** | `@bts-soft/upload` | `IUploadObserver` broadcasts lifecycle events (success, fail, delete) |
184
+ | **Chain of Responsibility** | `@bts-soft/common` | Interceptor chain: ClassSerializer, SqlInjection, GeneralResponse |
185
+ | **Adapter** | `@bts-soft/common` | ORM adapters for TypeORM, Mongoose, Sequelize, Prisma |
186
+ | **Decorator** | `@bts-soft/validation` | Composite decorators apply validation, transformation, and GraphQL metadata in one annotation |
187
+ | **Proxy / Deduplication** | `@bts-soft/common` | `IdempotencyInterceptor` returns cached Redis responses for duplicate requests |
188
+ | **Factory** | `@bts-soft/common` | `ConfigModule.forRoot()`, `ThrottlingModule.forRoot()`, `GraphqlModule.forRoot()` |
117
189
 
118
- ## Deep Dive: `@bts-soft/validation`
190
+ ### Three Core Design Pillars
119
191
 
120
- 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.
192
+ **1. Protocol Agnosticism**
193
+ Every component operates identically on REST (Express) and GraphQL (Apollo). Validation decorators apply `@Field()` metadata for GraphQL and omit it for REST using a single `isGraphql` flag. Rate limiting guards extract request context from both HTTP and Apollo execution contexts. The response formatter detects GraphQL contexts and skips HTTP envelope wrapping.
121
194
 
122
- ### Key Features
195
+ **2. Security by Default**
196
+ From the moment `setupInterceptors(app)` is called, every incoming request passes through SQL injection scanning before reaching any controller. Every text-based validation decorator applies a negative lookahead regex against common SQL keywords, double-hyphen comments, and statement terminators. Sensitive response fields are stripped by the `ClassSerializerInterceptor` before transmission.
123
197
 
124
- 1. **Declarative Security**: Every text-based decorator integrates the `SQL_INJECTION_REGEX` to filter malicious patterns.
125
- 2. **Smart Transformations**: Leverages `class-transformer` for automatic data normalization (e.g., auto-capitalization of names, lowercase emails, and digit-only national IDs).
126
- 3. **Protocol Agnostic**: Native support for both REST (Express) and GraphQL (Apollo), allowing a single DTO to serve both architectures via the `isGraphql` flag.
127
- 4. **Policy-Driven Passwords**: Comprehensive password validation with three configurable complexity levels.
198
+ **3. Extensibility Without Coupling**
199
+ Provider switching (Local disk to Cloudinary, Redis to in-memory, SMTP to SES) is achieved through environment variables and interface implementations, not code changes. New notification channels, upload strategies, or rate-limiting algorithms can be registered without modifying the core service.
128
200
 
129
201
  ---
130
202
 
131
- ### Core Decorators
132
-
133
- #### 1. Identity & Security
134
- - **`@PasswordField`**: Supports `ALPHANUMERIC`, `SYMBOLIC`, and `COMPREHENSIVE` complexity scenarios.
135
- - **`@NationalIdField`**: Specialized for Egyptian National IDs (14 digits) with auto-stripping of non-digits.
136
- - **`@IdField`**: Flexible validation for generic IDs (ULID, UUID, etc) with length enforcement.
203
+ ## Package: @bts-soft/validation
137
204
 
138
- #### 2. Text & Content
139
- - **`@NameField`**: Validates names and automatically applies title-case (e.g., "omar sabry" → "Omar Sabry").
140
- - **`@CapitalTextField`**: Generic text field with auto-capitalization for proper nouns like cities.
141
- - **`@TextField`**: Standard input field with length limits and strict SQL injection checks.
142
- - **`@DescriptionField`**: Designed for long-form content with support for newlines and special punctuation.
143
- - **`@UsernameField`**: Enforces system-level handle rules (3-30 chars, starts with a letter).
205
+ [![npm](https://img.shields.io/npm/v/@bts-soft/validation?style=flat-square&color=CB3837&logo=npm)](https://www.npmjs.com/package/@bts-soft/validation)
144
206
 
145
- #### 3. Contact & Primitives
146
- - **`@EmailField`**: Validates email format and normalizes input to lowercase.
147
- - **`@PhoneField`**: Validates international phone numbers using `libphonenumber-js` with regional support.
148
- - **`@UrlField`**: Validates web addresses and ensures lowercase normalization.
149
- - **`@NumberField`**: Supports integers and floats with flexible min/max constraints.
150
- - **`@BooleanField`**: Strict boolean checking for logic flags.
151
- - **`@DateField`**: Handles date strings and Date objects with automatic type conversion.
207
+ An enterprise-grade validation, sanitization, and rate-limiting toolkit. Decorators are composite: a single annotation applies structural validation, type checking, SQL injection prevention, and `class-transformer` normalization simultaneously. The package is protocol-agnostic: the same decorator annotates a REST DTO and a GraphQL `@InputType()` without modification.
152
208
 
153
- ---
209
+ ### Decorator Reference
154
210
 
155
- ### Implementation Example
211
+ #### Identity and Security
156
212
 
157
- ```typescript
158
- import { NameField, EmailField, PasswordField, PasswordComplexity } from '@bts-soft/validation';
213
+ | Decorator | Signature | Description |
214
+ | :--- | :--- | :--- |
215
+ | `@IdField` | `(id, length?, nullable?, isGraphql?, optional?)` | Validates fixed-length identifiers such as ULIDs (26 chars) or UUIDs. Checks SQLi patterns. |
216
+ | `@NationalIdField` | `(nullable?, isGraphql?, optional?)` | Egyptian National ID: exactly 14 digits, starts with `2` or `3`. Strips non-digits automatically. |
217
+ | `@PasswordField` | `(min?, max?, complexity?, nullable?, isGraphql?, optional?, checkSql?)` | Configurable complexity: `ALPHANUMERIC`, `SYMBOLIC`, or `COMPREHENSIVE`. |
159
218
 
160
- export class RegisterUserDto {
161
- @NameField('Full Name')
162
- name: string;
219
+ Password complexity levels:
163
220
 
164
- @EmailField()
165
- email: string;
221
+ | Level | Rules |
222
+ | :--- | :--- |
223
+ | `ALPHANUMERIC` | At least one uppercase, one lowercase, one digit |
224
+ | `SYMBOLIC` | At least one uppercase, one lowercase, one special character |
225
+ | `COMPREHENSIVE` | At least one uppercase, one lowercase, one digit, and one special character |
166
226
 
167
- @PasswordField(12, 32, PasswordComplexity.COMPREHENSIVE)
168
- password: string;
169
- }
170
- ```
227
+ #### String and Text
171
228
 
172
- ---
229
+ | Decorator | Signature | Auto-Transform | Description |
230
+ | :--- | :--- | :--- | :--- |
231
+ | `@TextField` | `(text, min?, max?, nullable?, isGraphql?, optional?, checkSql?)` | Lowercase | General-purpose input. Allows Latin, Arabic, digits, spaces, basic punctuation. |
232
+ | `@CapitalTextField` | `(text, min?, max?, nullable?, isGraphql?, optional?, checkSql?)` | Title Case | Proper nouns: city names, countries, titles. Letters and Arabic only. |
233
+ | `@NameField` | `(text, min?, max?, nullable?, isGraphql?, optional?, checkSql?)` | Title Case | Human names. Latin and Arabic alphabets only. |
234
+ | `@UsernameField` | `(nullable?, isGraphql?, optional?, checkSql?)` | None | 3-30 chars, alphanumeric and underscore, must start with a letter. |
235
+ | `@DescriptionField` | `(text, min?, max?, nullable?, isGraphql?, optional?, checkSql?)` | Lowercase | Long-form content. Allows newlines, punctuation, Arabic text. Default 10-2000 chars. |
173
236
 
237
+ #### Contact and Web
174
238
 
175
- ### Utility Exports
239
+ | Decorator | Signature | Auto-Transform | Description |
240
+ | :--- | :--- | :--- | :--- |
241
+ | `@EmailField` | `(nullable?, isGraphql?, optional?)` | Lowercase | RFC-compliant email validation with SQLi check. |
242
+ | `@PhoneField` | `(format?, nullable?, isGraphql?, optional?, checkSql?)` | Strip non-digits | Validates against ISO country code using `libphonenumber-js`. Default: `EG`. |
243
+ | `@UrlField` | `(text?, nullable?, isGraphql?, optional?, checkSql?)` | Lowercase | Enforces URL structure including protocol. |
176
244
 
177
- The validation package also exports the underlying transformation functions for manual use:
245
+ #### Primitives
178
246
 
179
- - `LowerWords(value: string)`: Converts strings to lowercase.
180
- - `CapitalizeWords(value: string)`: Converts strings to Title Case.
247
+ | Decorator | Signature | GraphQL Type | Description |
248
+ | :--- | :--- | :--- | :--- |
249
+ | `@NumberField` | `(text, min?, max?, isInt?, nullable?, isGraphql?, optional?)` | `Int` or `Float` | Range-bounded numeric validation. |
250
+ | `@BooleanField` | `(nullable?, isGraphql?, optional?)` | `Boolean` | Strict boolean enforcement. |
251
+ | `@DateField` | `(text, nullable?, isGraphql?, optional?)` | `Date` | ISO string or epoch number parsed into `Date` via `class-transformer`. |
252
+ | `@EnumField` | `(enumType, name, nullable?, isGraphql?, optional?)` | Enum | Validates value exists within the TypeScript enum's values. |
181
253
 
182
- ---
254
+ ### Usage Examples
183
255
 
184
- ## Deep Dive: `@bts-soft/cache`
256
+ ```typescript
257
+ import {
258
+ NameField, EmailField, PasswordField, PasswordComplexity,
259
+ PhoneField, IdField, DescriptionField, NumberField,
260
+ } from '@bts-soft/core';
185
261
 
186
- 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.
262
+ export class RegisterUserDto {
263
+ @NameField('Full Name', 2, 80, false, false, false)
264
+ name: string; // "omar sabry" -> "Omar Sabry"
187
265
 
188
- ---
266
+ @EmailField(false, false, false)
267
+ email: string; // "USER@Example.COM" -> "user@example.com"
189
268
 
190
- ### Key Capabilities
269
+ @PasswordField(12, 32, PasswordComplexity.COMPREHENSIVE, false, false, false)
270
+ password: string;
191
271
 
192
- 1. **Atomic Distributed Locking**: Prevents race conditions in distributed environments using `acquireLock` and `waitForLock`.
193
- 2. **Reliable Pub/Sub**: High-speed, multi-channel event distribution with dedicated subscriber connections to ensure non-blocking operations.
194
- 3. **Complex Data Structures**: Native support for Hashes, Sets, Sorted Sets (Rankings), and Lists.
195
- 4. **Advanced Analytics**: Geospatial indexing for "nearby" features and HyperLogLog for memory-efficient unique counting.
196
- 5. **100% Verified Infrastructure**: Every operation is backed by comprehensive E2E tests running on real Redis instances.
272
+ @PhoneField('EG', false, false, true)
273
+ phone: string; // "+20 101 234 5678" -> "+201012345678"
274
+ }
275
+ ```
197
276
 
198
- ---
277
+ For GraphQL `@InputType()` classes, set `isGraphql = true` (or omit the flag, since `true` is the default):
199
278
 
200
- ### Core Operations API
279
+ ```typescript
280
+ import { InputType } from '@nestjs/graphql';
281
+ import { NameField, EmailField, PasswordField, PasswordComplexity } from '@bts-soft/core';
201
282
 
202
- #### 1. Standard Caching
203
- - `set(key, value, ttl?)`: Stores values with automatic JSON serialization (Default TTL: 1 hour).
204
- - `setForever(key, value)`: Bypasses default TTLs for permanent configuration or state storage.
205
- - `get<T>(key)`: Retrieves and automatically parses JSON into typed objects.
206
- - `del(key)`: Removes keys from the database.
283
+ @InputType()
284
+ export class RegisterInput {
285
+ @NameField('Full Name')
286
+ name: string;
207
287
 
208
- #### 2. Atomic Numeric Operations
209
- - `incr(key)` / `decr(key)`: Atomically increments or decrements integers.
210
- - `incrByFloat(key, n)`: Handles precise floating-point increments.
288
+ @EmailField()
289
+ email: string;
211
290
 
212
- #### 3. Distributed Locking Example
213
- ```typescript
214
- const lockValue = crypto.randomUUID();
215
- const acquired = await redisService.acquireLock('order:process:123', lockValue, 5000);
216
-
217
- if (acquired) {
218
- try {
219
- // Critical section logic
220
- } finally {
221
- await redisService.releaseLock('order:process:123', lockValue);
222
- }
291
+ @PasswordField(8, 32, PasswordComplexity.COMPREHENSIVE)
292
+ password: string;
223
293
  }
224
294
  ```
225
295
 
226
- #### 4. Real-time Pub/Sub Example
227
- ```typescript
228
- // Subscriber (Dedicated connection handled automatically)
229
- await redisService.subscribe('system_updates', (msg) => {
230
- console.log('Update received:', JSON.parse(msg));
231
- });
232
-
233
- // Publisher
234
- await redisService.publish('system_updates', { status: 'OK', timestamp: Date.now() });
235
- ```
296
+ ### Rate Limiting Suite
236
297
 
237
- ---
298
+ The package implements all five rate-limiting algorithms from *System Design Interview Vol. 1, Chapter 4* as a pluggable NestJS guard. Both REST and GraphQL contexts are supported natively.
238
299
 
239
- ### Data Structure Reference
300
+ ```
301
+ Incoming Request
302
+ │
303
+ ▼
304
+ RateLimiterGuard
305
+ │
306
+ ▼
307
+ Extract Client Key (IP / JWT user ID / API key)
308
+ │
309
+ ▼
310
+ algorithm.consume(key)
311
+ │
312
+ ├── [Allowed] ──► Set X-RateLimit-* Headers ──► Pass to Handler
313
+ │
314
+ └── [Denied] ──► Set Retry-After Header ──► 429 HttpException
315
+ ```
240
316
 
241
- | Structure | Common Methods | Best For |
317
+ | Algorithm | Storage Complexity | Best For |
242
318
  | :--- | :--- | :--- |
243
- | **Hashes** | `hSet`, `hGetAll`, `hIncrBy` | Object storage, User profiles |
244
- | **Sorted Sets** | `zAdd`, `zRange`, `zScore` | Leaderboards, Scored feeds |
245
- | **Sets** | `sAdd`, `sMembers`, `sIsMember` | Unique tags, Permissions |
246
- | **Lists** | `lPush`, `rPop`, `lTrim` | Queues, Activity history |
247
- | **Geo** | `geoAdd`, `geoDist`, `geoPos` | Nearby stores, Proximity search |
319
+ | **Token Bucket** | O(1) per key | General APIs with burst tolerance |
320
+ | **Leaking Bucket** | O(1) per key | Smooth, predictable outflow for payment APIs |
321
+ | **Fixed Window Counter** | O(1) per key | Low-overhead login attempt limits |
322
+ | **Sliding Window Log** | O(limit) per key | High-security endpoints requiring absolute precision |
323
+ | **Sliding Window Counter** | O(1) per key | High-scale endpoints needing precision without log overhead |
248
324
 
249
- ---
325
+ Storage backends auto-negotiate: if a Redis connection is available via `@bts-soft/cache`, the distributed `RedisStore` is used. On connection failure, the guard transparently falls back to an in-memory store.
250
326
 
251
- ## Deep Dive: @bts-soft/notifications
327
+ ```typescript
328
+ import { RateLimit, RateLimiterAlgorithm } from '@bts-soft/core';
329
+
330
+ @Controller('payments')
331
+ export class PaymentController {
332
+ @RateLimit({
333
+ algorithm: RateLimiterAlgorithm.SLIDING_WINDOW_LOG,
334
+ limit: 5,
335
+ windowMs: 60_000,
336
+ keyExtractor: (req) => req.user?.id ?? req.ip,
337
+ })
338
+ @Post('charge')
339
+ async chargeCard() {}
340
+ }
341
+ ```
252
342
 
253
- 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.
343
+ Response headers appended on every rate-limited response:
254
344
 
255
- ---
345
+ | Header | Description |
346
+ | :--- | :--- |
347
+ | `X-RateLimit-Limit` | Maximum requests per window |
348
+ | `X-RateLimit-Remaining` | Remaining requests in current window |
349
+ | `X-RateLimit-Reset` | Unix timestamp when the window resets |
350
+ | `Retry-After` | Seconds to wait before retrying (only on 429) |
256
351
 
257
- ### Reliability Engineering: The Queue System
352
+ ### SQL Injection Prevention
258
353
 
259
- All notifications are processed asynchronously using BullMQ and Redis. This architecture provides several critical benefits:
354
+ The `SQL_INJECTION_REGEX` applied to every string field is:
260
355
 
261
- 1. **Non-Blocking Execution**: The API returns a response immediately after the job is queued, decoupling user experience from external provider latency.
262
- 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.
263
- 3. **Global Retry Policies**: Integrated with exponential backoff strategies to handle temporary infrastructure failures gracefully.
356
+ ```
357
+ /^(?!.*(SELECT|INSERT|DELETE|UPDATE|DROP|UNION|EXEC|TRUNCATE|ALTER|CREATE|--|;)).*$/i
358
+ ```
359
+
360
+ The negative lookahead `(?! ...)` fails validation the moment any SQL keyword, double-hyphen comment sequence, or statement terminator is detected anywhere in the input string. The check is case-insensitive to catch obfuscated payloads.
264
361
 
265
362
  ---
266
363
 
267
- ### Channel Capabilities (8+ Integrated Protocols)
364
+ ## Package: @bts-soft/cache
268
365
 
269
- #### 1. Email (EMAIL)
270
- - **Technology**: Nodemailer.
271
- - **Support**: Standard SMTP, SES, Gmail, and custom transport configurations.
272
- - **Dynamic Overrides**: Support for per-message SMTP configuration and attachments.
366
+ [![npm](https://img.shields.io/npm/v/@bts-soft/cache?style=flat-square&color=CB3837&logo=npm)](https://www.npmjs.com/package/@bts-soft/cache)
273
367
 
274
- #### 2. WhatsApp (WHATSAPP)
275
- - **Provider**: Twilio WhatsApp Business API.
276
- - **Normalizer**: Automatic handling of regional phone number formats.
368
+ A production-grade Redis abstraction layer built on the Modular Facade Pattern. Developers interact with a single `RedisService` injectable while the internal architecture distributes responsibility across 15 single-responsibility domain services.
277
369
 
278
- #### 3. SMS (SMS)
279
- - **Provider**: Twilio SMS.
280
- - **Reliability**: Optimized for high-deliverability transactional messaging.
370
+ ### Module Initialization Flow
281
371
 
282
- #### 4. Telegram (TELEGRAM)
283
- - **Technology**: Telegram Bot API.
284
- - **Features**: MarkdownV2 and HTML parsing support, real-time bot integration.
372
+ ```
373
+ NestJS App ──► RedisConfigService
374
+ │ resolve REDIS_HOST / PORT / PASSWORD / DB
375
+ ▼
376
+ createRedisClient
377
+ │
378
+ ▼
379
+ node-redis v4 ──TCP──► Redis Server
380
+ │
381
+ ◄── Connection confirmed
382
+
383
+ NestJS App ──► RedisHealth.onModuleInit()
384
+ │ PING
385
+ ▼
386
+ Redis Server
387
+ │ PONG
388
+ ▼
389
+ Module ready
390
+ ```
285
391
 
286
- #### 5. Firebase Push (FIREBASE_FCM)
287
- - **Technology**: Firebase Admin SDK.
288
- - **Support**: Native Android/iOS push notifications and web push.
392
+ ### 15 Internal Domain Services
289
393
 
290
- #### 6. Discord (DISCORD)
291
- - **Mechanism**: High-performance Webhook integration.
292
- - **Customization**: Support for complex embeds and custom bot identities.
394
+ | Domain Service | Operations |
395
+ | :--- | :--- |
396
+ | **CoreRedisService** | `set`, `setForever`, `get`, `getOrSet`, `del`, `update`, `mSet`, `setNX` |
397
+ | **StringRedisService** | `getSet`, `strlen`, `append`, `getRange`, `setRange`, `mGet` |
398
+ | **NumberRedisService** | `incr`, `incrBy`, `incrByFloat`, `decr`, `decrBy` |
399
+ | **HashRedisService** | `hSet`, `hGet`, `hGetAll`, `hDel`, `hExists`, `hKeys`, `hVals`, `hLen`, `hIncrBy`, `hIncrByFloat`, `hSetNX` |
400
+ | **ListORedisService** | `lPush`, `rPush`, `lPop`, `rPop`, `lRange`, `lLen`, `lIndex`, `lInsert`, `lRem`, `lTrim`, `rPopLPush`, `lSet`, `lPos` |
401
+ | **OperationRedisService** | `sAdd`, `sRem`, `sMembers`, `sIsMember`, `sCard`, `sPop`, `sMove`, `sDiff`, `sInter`, `sUnion` and store variants |
402
+ | **SortedORedisService** | `zAdd`, `zRange`, `zRangeByScore`, `zRevRange`, `zCard`, `zScore`, `zRank`, `zRevRank`, `zIncrBy`, `zRem`, `zRemRangeByRank`, `zRemRangeByScore`, `zCount`, `zUnionStore`, `zInterStore` |
403
+ | **GeoRedisService** | `geoAdd`, `geoPos`, `geoDist`, `geoHash`, `geoRemove` |
404
+ | **HyperLogLogRedisService** | `pfAdd`, `pfCount`, `pfMerge`, `pfDebug`, `pfClear` |
405
+ | **LockRedisService** | `acquireLock`, `releaseLock`, `extendLock`, `isLocked`, `getLockValue`, `waitForLock` |
406
+ | **PubSubRedisService** | `publish`, `subscribe`, `pSubscribe`, `unsubscribe`, `pUnsubscribe`, `getSubscriptions`, `getChannels`, `getSubCount`, `createMessageHandler` |
407
+ | **TransactionRedisService** | `multiExecute`, `watch`, `unwatch`, `withTransaction`, `discard`, `transactionGetSet` |
408
+ | **StreamRedisService** | `xAdd`, `xRead`, `xGroupCreate`, `xReadGroup`, `xAck`, `xLen` |
409
+ | **BitmapRedisService** | `setBit`, `getBit`, `bitCount`, `bitOp` |
410
+ | **UtilityRedisService** | `exists`, `expire`, `ttl`, `persist`, `pttl`, `delByPattern`, `eval` |
411
+
412
+ ### Setup
293
413
 
294
- #### 7. Microsoft Teams (TEAMS)
295
- - **Mechanism**: Incoming Webhooks using adaptive message cards.
414
+ ```typescript
415
+ import { RedisModule } from '@bts-soft/core';
296
416
 
297
- #### 8. Facebook Messenger (MESSENGER)
298
- - **Technology**: Meta Graph API.
299
- - **Usage**: Support for Page-Scoped IDs (PSID) and standard messaging blocks.
417
+ @Module({
418
+ imports: [RedisModule],
419
+ })
420
+ export class AppModule {}
421
+ ```
300
422
 
301
- ---
423
+ Environment variables:
302
424
 
303
- ### Enterprise Features
425
+ | Variable | Default | Required | Description |
426
+ | :--- | :--- | :--- | :--- |
427
+ | `REDIS_HOST` | `localhost` | No | Redis hostname |
428
+ | `REDIS_PORT` | `6379` | No | Redis port |
429
+ | `REDIS_PASSWORD` | — | No | Authentication password |
430
+ | `REDIS_DB` | `0` | No | Database index (0-15) |
431
+ | `REDIS_TTL` | `3600` | No | Default TTL in seconds |
304
432
 
305
- #### Templating and Localization
306
- - **Handlebars Engine**: Centralized template rendering for all channels via TemplateService.
307
- - **Dynamic I18n**: Support for per-recipient language resolution using nestjs-i18n.
308
- - **Contextual Data**: Pass complex objects to templates for real-time personalization.
433
+ ### Core API Reference
309
434
 
310
- #### Architecture and Extensibility
311
- - **Channel Registry**: A plugin-based system that allows for dynamic channel registration and discovery.
312
- - **BullMQ Priority**: Native support for job priorities (1-100) to ensure critical alerts are processed before marketing broadcasts.
313
- - **Comprehensive Testing**: Backed by a 100% coverage unit testing suite and standalone Docker-based E2E verification.
435
+ #### Key-Value Operations
314
436
 
315
- ---
437
+ | Method | Signature | Description |
438
+ | :--- | :--- | :--- |
439
+ | `set` | `(key, value, ttl?)` | Store any value with automatic JSON serialization. Default TTL: 3600s. |
440
+ | `setForever` | `(key, value)` | Store without expiration. For permanent configuration state. |
441
+ | `get` | `<T>(key)` | Retrieve and automatically JSON-parse into typed object. |
442
+ | `getOrSet` | `<T>(key, factory, ttl?)` | Cache-aside: returns cached value or calls factory and caches result. |
443
+ | `del` | `(key)` | Delete a key. |
444
+ | `update` | `(key, value, ttl?)` | Delete and re-set atomically. |
445
+ | `mSet` | `(data)` | Multi-set via Redis pipeline. |
446
+ | `setNX` | `(key, value, ttl?)` | Set only if the key does not exist. Returns boolean. |
447
+ | `exists` | `(key)` | Returns true if the key exists. |
448
+ | `expire` | `(key, seconds)` | Update TTL on an existing key. |
449
+ | `ttl` | `(key)` | Returns remaining TTL in seconds. |
450
+
451
+ #### Atomic Counters
452
+
453
+ | Method | Signature | Description |
454
+ | :--- | :--- | :--- |
455
+ | `incr` | `(key)` | Atomically increment by 1. |
456
+ | `incrBy` | `(key, n)` | Atomically increment by integer N. |
457
+ | `incrByFloat` | `(key, n)` | Atomically increment by float N. |
458
+ | `decr` | `(key)` | Atomically decrement by 1. |
459
+ | `decrBy` | `(key, n)` | Atomically decrement by integer N. |
316
460
 
461
+ #### Distributed Locking
317
462
 
318
- ## Deep Dive: `@bts-soft/upload`
463
+ All locks use Lua scripts for atomic token verification. A lock can only be released by the exact process that acquired it.
319
464
 
320
- 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.
465
+ ```
466
+ Service A ──► Redis: acquireLock("lock:order:99", "token-A", 10000)
467
+ Redis ──► Service A: OK (lock acquired)
321
468
 
322
- ### Multi-Provider Architecture
469
+ Service B ──► Redis: acquireLock("lock:order:99", "token-B", 10000)
470
+ Redis ──► Service B: null (lock denied — already held by A)
323
471
 
324
- The service leverages the **Strategy Pattern** to support multiple storage backends without changing the application logic:
472
+ [Service A executes critical business logic]
325
473
 
326
- 1. **Cloudinary Strategy**: Default for production, providing on-the-fly optimization, transcoding, and CDN delivery.
327
- 2. **Local Disk Strategy**: Ideal for development or high-security environments where files must remain on the internal network.
474
+ Service A ──► Redis: releaseLock("lock:order:99", "token-A")
475
+ Redis: Lua script verifies token-A matches stored token
476
+ Redis ──► Service A: 1 (key deleted)
328
477
 
329
- Switching providers is handled via the `UPLOAD_PROVIDER` environment variable.
478
+ Service B ──► Redis: acquireLock("lock:order:99", "token-B", 10000)
479
+ Redis ──► Service B: OK (lock acquired)
480
+ ```
330
481
 
331
- ### Design Patterns
482
+ ```typescript
483
+ import { Injectable } from '@nestjs/common';
484
+ import { RedisService } from '@bts-soft/core';
485
+ import { randomUUID } from 'crypto';
332
486
 
333
- The module is built on three pillars of software engineering:
487
+ @Injectable()
488
+ export class PaymentService {
489
+ constructor(private readonly redis: RedisService) {}
334
490
 
335
- - **Strategy Pattern**: The `IUploadStrategy` and `IDeleteStrategy` interfaces decouple the service from the underlying storage technology.
336
- - **Command Pattern**: Each operation (Upload, Delete) is encapsulated in a command object, ensuring consistent execution and error handling.
337
- - **Observer Pattern**: Triggers events on success or failure, enabling centralized logging and auditing via `IUploadObserver`.
491
+ async processOrder(orderId: string): Promise<void> {
492
+ const lockKey = `lock:order:${orderId}`;
493
+ const token = randomUUID();
338
494
 
339
- ### Media Specifications & Validation
495
+ const acquired = await this.redis.acquireLock(lockKey, token, 10_000);
496
+ if (!acquired) return; // Another instance is already processing this order
340
497
 
341
- The system enforces strict validation for file extensions and sizes. Limits can be customized via environment variables.
498
+ try {
499
+ await this.runPaymentGateway(orderId);
500
+ } finally {
501
+ await this.redis.releaseLock(lockKey, token);
502
+ }
503
+ }
504
+ }
505
+ ```
342
506
 
343
- | Media Type | File Extensions | Default Limit | Storage Logic |
344
- | :--- | :--- | :--- | :--- |
345
- | **Images** | `jpg`, `png`, `webp`, `gif` | 5 MB | Auto-optimization and non-destructive resizing. |
346
- | **Videos** | `mp4`, `webm`, `avi`, `mov` | 100 MB | Chunked upload support with duration extraction. |
347
- | **Audio** | `mp3`, `wav`, `ogg`, `m4a` | 50 MB | Optimized for playback and metadata extraction. |
348
- | **Raw Files** | `pdf`, `doc`, `zip`, `txt` | 10 MB | Stored as 'raw' binary with original headers. |
349
- | **3D Models** | `glb`, `gltf`, `fbx`, `obj`, `stl` | 100 MB | Stored as binary models with high-capacity limits. |
507
+ #### Pub/Sub
350
508
 
351
- ### Configuration Reference
509
+ The subscriber uses a dedicated, isolated connection separate from the main command connection to prevent blocking.
352
510
 
511
+ ```
512
+ ┌─────────────────────────┐ ┌────────────────────────────┐
513
+ │ Publisher Service │ │ Subscriber Service │
514
+ │ │ │ │
515
+ │ Publisher Code │ │ Dedicated Sub Connection │
516
+ │ │ │ │ │ │
517
+ │ │ PUBLISH │ │ │ Deliver │
518
+ │ ▼ │ │ ▼ │
519
+ │ Primary Connection ────┼──────► │ Redis Server │
520
+ │ │ network │ │ │
521
+ └─────────────────────────┘ │ │ Parse JSON │
522
+ │ ▼ │
523
+ │ Handler Callback │
524
+ └────────────────────────────┘
525
+ ```
353
526
 
354
- ```env
355
- # Provider Selection
356
- UPLOAD_PROVIDER=cloudinary # or 'local'
527
+ ```typescript
528
+ // Subscribe on module init
529
+ await redisService.subscribe('events:orders', (data, channel) => {
530
+ console.log(`Event on ${channel}:`, data);
531
+ });
357
532
 
358
- # Cloudinary Credentials (if provider=cloudinary)
359
- CLOUDINARY_CLOUD_NAME=your_name
360
- CLOUDINARY_API_KEY=your_key
361
- CLOUDINARY_API_SECRET=your_secret
533
+ // Wildcard pattern subscription
534
+ await redisService.pSubscribe('user:*', (data, channel) => {
535
+ console.log(`Pattern match on ${channel}:`, data);
536
+ });
362
537
 
363
- # Local Settings (if provider=local)
364
- UPLOAD_LOCAL_PATH=./uploads
538
+ // Publish from any service
539
+ await redisService.publish('events:orders', { orderId: '123', status: 'shipped' });
365
540
  ```
366
541
 
367
- ---
368
-
369
- ## Deep Dive: `@bts-soft/common`
542
+ #### Sorted Sets: Leaderboards
370
543
 
371
- 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.
372
-
373
- ---
544
+ ```typescript
545
+ await redis.zAdd('leaderboard:weekly', 9850, 'user:alice');
546
+ await redis.zIncrBy('leaderboard:weekly', 150, 'user:alice');
547
+ const top10 = await redis.zRevRange('leaderboard:weekly', 0, 9);
548
+ const rank = await redis.zRevRank('leaderboard:weekly', 'user:alice');
549
+ ```
374
550
 
375
- ### Standardized Global Interceptors
551
+ #### Bitmaps: Daily Active Users
376
552
 
377
- By calling `setupInterceptors(app)` in your `main.ts`, you enable a powerful suite of request-processing logic:
553
+ ```typescript
554
+ // Record login for user ID 4201 on a specific date
555
+ await redis.setBit('dau:2025-01-15', 4201, 1);
378
556
 
379
- 1. **`ClassSerializerInterceptor`**:
380
- Uses `class-transformer` to filter out sensitive fields (marked with `@Exclude()`) and include computed properties (marked with `@Expose()`).
381
-
382
- 2. **`SqlInjectionInterceptor`**:
383
- 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.
557
+ // Check if user was active
558
+ const active = await redis.getBit('dau:2025-01-15', 4201); // 0 or 1
384
559
 
385
- 3. **`GeneralResponseInterceptor`**:
386
- 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.
560
+ // Count total active users for the day
561
+ const total = await redis.bitCount('dau:2025-01-15');
387
562
 
388
- #### The Standard Response Envelope
389
- ```json
390
- {
391
- "success": true,
392
- "statusCode": 200,
393
- "message": "Request successful",
394
- "timeStamp": "2024-03-21T10:00:00.000Z",
395
- "data": { ... },
396
- "items": [ ... ],
397
- "pagination": {
398
- "total": 100,
399
- "page": 1,
400
- "limit": 10
401
- }
402
- }
563
+ // Weekly active users using bitwise OR across daily bitmaps
564
+ await redis.bitOp('OR', 'wau:2025-W03', 'dau:2025-01-13', 'dau:2025-01-14', 'dau:2025-01-15');
403
565
  ```
404
566
 
405
- ---
567
+ #### HyperLogLog: Unique Visitor Counting
406
568
 
407
- ### Shared Base Classes
569
+ ```typescript
570
+ // Uses approximately 12KB of memory regardless of unique visitor count
571
+ await redis.pfAdd('hll:page:/home', visitorId);
572
+ const uniqueCount = await redis.pfCount('hll:page:/home');
408
573
 
409
- #### 1. `BaseEntity` (The Persistence Foundation)
410
- All database entities in the system should extend a specialized base class.
574
+ // Merge multiple HyperLogLogs
575
+ await redis.pfMerge('hll:all-pages', 'hll:page:/home', 'hll:page:/about');
576
+ ```
411
577
 
412
- - **ULID Integration**: Instead of predictable numeric IDs, it uses ULIDs (Universally Unique Lexicographically Sortable Identifiers) which are both unique and sortable.
413
- - **Audit Trails**: Automatically generates `createdAt` and `updatedAt` timestamps.
414
- - **Multi-ORM Support**: Includes dedicated bases for TypeORM, Sequelize, Mongoose, Prisma, and GraphQL.
578
+ #### Redis Streams: Reliable Event Sourcing
415
579
 
416
- #### 2. `BaseResponse` (The API Contract)
417
- Used as a base class for DTOs and GraphQL Object Types to ensure consistency in response construction.
580
+ ```typescript
581
+ // Producer appends events
582
+ const messageId = await redis.xAdd('stream:orders', { orderId: '123', amount: '99.99' });
583
+
584
+ // Create consumer group (idempotent)
585
+ try {
586
+ await redis.xGroupCreate('stream:orders', 'workers', '$', true);
587
+ } catch { /* Group already exists */ }
588
+
589
+ // Consumer reads and acknowledges
590
+ const result = await redis.xReadGroup(
591
+ 'workers', 'worker-1',
592
+ [{ key: 'stream:orders', id: '>' }],
593
+ 10, // Max messages
594
+ 5000, // Block for 5 seconds if no messages
595
+ );
596
+ await redis.xAck('stream:orders', 'workers', result[0].id);
597
+ ```
418
598
 
419
599
  ---
420
600
 
421
- ### Infrastructure Modules
422
-
423
- The common package provides pre-configured NestJS modules:
601
+ ## Package: @bts-soft/notifications
424
602
 
425
- - **`ConfigModule`**: A wrapper around `@nestjs/config` with built-in validation.
426
- - **`ThrottlingModule`**: Robust rate limiting that automatically handles both HTTP and GraphQL contexts.
427
- - **`TranslationModule`**: Integrated `nestjs-i18n` support for multi-language applications (Arabic/English).
428
- - **`GraphqlModule`**: Standard Apollo Server setup with custom error filters and a context that includes both request and response objects.
603
+ [![npm](https://img.shields.io/npm/v/@bts-soft/notifications?style=flat-square&color=CB3837&logo=npm)](https://www.npmjs.com/package/@bts-soft/notifications)
429
604
 
430
- ---
605
+ A high-availability multi-channel notification engine. All dispatch operations are queued via BullMQ and processed asynchronously, decoupling API response times from external provider latency.
431
606
 
432
- ### Production & CLI Utilities
607
+ ### Architecture
433
608
 
434
- The common package exposes tools to manage environment-specific behaviors:
609
+ ```
610
+ notificationService.send()
611
+ │
612
+ ▼ PRE-FLIGHT PIPELINE (synchronous)
613
+ ┌─────────────────────────────────────────────────────┐
614
+ │ Expired? ──[yes]──► Discard + Log EXPIRED │
615
+ │ Duplicate key? ──[yes]──► Skip + Log DUPLICATE │
616
+ │ User opted out? ──[yes]──► Skip + Log OPTED_OUT │
617
+ │ Rate limited? ──[yes]──► Skip + Log RATE_LIMITED│
618
+ └─────────────────────────────────────────────────────┘
619
+ │ [all checks pass]
620
+ ▼
621
+ Enqueue BullMQ Job ──► API returns 200 immediately to caller
622
+ │
623
+ ▼ WORKER PIPELINE (asynchronous)
624
+ ┌─────────────────────────────────────────────────────┐
625
+ │ Job expired? ──[yes]──► Update log EXPIRED │
626
+ │ │
627
+ │ Apply i18n + Handlebars template render │
628
+ │ Resolve channel from factory │
629
+ │ Dispatch to provider │
630
+ │ ├── [Success] Update log SENT │
631
+ │ ├── [4xx client err] Update log FAILED │
632
+ │ └── [5xx server err] BullMQ exponential retry │
633
+ └─────────────────────────────────────────────────────┘
634
+ ```
435
635
 
436
- - **`disableConsoleInProduction()`**: A safety utility to mute all `console` outputs when running in production.
437
- - **`displayAppBanner(appName, port)`**: Displays a clean, professional banner in the terminal when the server starts.
636
+ ### Supported Channels
438
637
 
439
- ---
638
+ | Channel | Technology | Provider |
639
+ | :--- | :--- | :--- |
640
+ | `EMAIL` | Nodemailer | SMTP, Gmail, Outlook, SendGrid, Amazon SES |
641
+ | `SMS` | Twilio / SMS Misr / Vonage | Configurable via `SMS_PROVIDER` env var |
642
+ | `WHATSAPP` | Twilio WhatsApp Business API | E.164 normalization with Egypt fallback |
643
+ | `FIREBASE_FCM` | Firebase Admin SDK | Android, iOS, Web push |
644
+ | `TELEGRAM` | Telegram Bot API | Markdown V2 and HTML parse modes |
645
+ | `DISCORD` | Webhook | Embeds, custom bot identity |
646
+ | `TEAMS` | Incoming Webhooks | Adaptive Message Cards |
647
+ | `MESSENGER` | Meta Graph API | Page-Scoped IDs (PSID) |
648
+ | `SLACK` | Webhook / Slack Web API | Bot messages, channel targeting |
649
+ | `ONESIGNAL` | OneSignal REST API | Cross-platform push notifications |
650
+ | `WEB_PUSH` | VAPID / Web Push Standard | Browser push notifications |
651
+ | `IN_APP` | Pusher Channels | Real-time in-app events |
652
+ | `WEBHOOK` | HTTP POST | Generic signed webhook dispatch |
653
+
654
+ ### Setup
440
655
 
656
+ ```typescript
657
+ import { Module } from '@nestjs/common';
658
+ import { RedisModule, NotificationModule } from '@bts-soft/core';
441
659
 
442
- ---
660
+ @Module({
661
+ imports: [
662
+ RedisModule,
663
+ NotificationModule,
664
+ ],
665
+ })
666
+ export class AppModule {}
667
+ ```
443
668
 
444
- ## Scenario-Based Integration Guides
669
+ ### Sending Notifications
445
670
 
446
- To understand the full power of `@bts-soft/core`, let's look at how these modules interact in real-world scenarios.
671
+ ```typescript
672
+ import { Injectable } from '@nestjs/common';
673
+ import { NotificationService, ChannelType } from '@bts-soft/core';
447
674
 
448
- ### Scenario A: Building a "User Registration" Flow
675
+ @Injectable()
676
+ export class OrderService {
677
+ constructor(private readonly notifications: NotificationService) {}
678
+
679
+ async onOrderCompleted(email: string, orderId: string) {
680
+ // Single channel with Handlebars template
681
+ await this.notifications.send(ChannelType.EMAIL, {
682
+ recipientId: email,
683
+ subject: 'Order Confirmed',
684
+ body: 'Hi {{name}}, your order #{{orderId}} has been confirmed.',
685
+ context: { name: 'Omar', orderId },
686
+ });
687
+ }
449
688
 
450
- This scenario demonstrates the interaction between **Validation**, **Cache**, **Common**, and **Notifications**.
689
+ async alertTeam(message: string) {
690
+ // Multi-channel bulk dispatch
691
+ await this.notifications.sendBulk([
692
+ { channel: ChannelType.SLACK, message: { recipientId: '#alerts', body: message } },
693
+ { channel: ChannelType.DISCORD, message: { body: message } },
694
+ { channel: ChannelType.TEAMS, message: { body: message } },
695
+ ]);
696
+ }
697
+ }
698
+ ```
451
699
 
452
- 1. **Input Validation**:
453
- 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.
454
-
455
- 2. **Duplicate Check (Cache)**:
456
- Before hitting the database, the service checks Redis using `redisService.exists('registration:lock:' + email)` to prevent rapid-fire duplicate registrations (Idempotency).
700
+ ### Database Integration for Notification Logs
457
701
 
458
- 3. **Persistence (Common)**:
459
- The `User` entity extends `BaseEntity`. It is saved with a auto-generated ULID.
702
+ Implement `INotificationLogRepository` and register it using the `NOTIFICATION_LOG_REPOSITORY` injection token to persist logs to any database:
460
703
 
461
- 4. **Welcome Message (Notifications)**:
462
- A background job is queued via `notificationService.send(ChannelType.EMAIL, ...)`. The background processor handles the Nodemailer handshake while the API returns a response.
704
+ ```typescript
705
+ import { Injectable } from '@nestjs/common';
706
+ import { InjectRepository } from '@nestjs/typeorm';
707
+ import { Repository } from 'typeorm';
708
+ import { INotificationLogRepository, NotificationLog } from '@bts-soft/core';
463
709
 
464
- 5. **Response Formatting (Common)**:
465
- The `GeneralResponseInterceptor` catches the return value and wraps it in the standard success envelope before sending it to the client.
710
+ @Injectable()
711
+ export class DbNotificationLogRepo implements INotificationLogRepository {
712
+ constructor(
713
+ @InjectRepository(NotificationLogEntity)
714
+ private readonly repo: Repository<NotificationLogEntity>,
715
+ ) {}
466
716
 
467
- ### Scenario B: Building an "Image Gallery with Search"
717
+ async create(log: Omit<NotificationLog, 'id'>): Promise<NotificationLog> {
718
+ return this.repo.save(this.repo.create(log));
719
+ }
468
720
 
469
- This scenario demonstrates **Upload**, **Validation**, and **Cache**.
721
+ async updateByJobId(jobId: string, update: Partial<NotificationLog>): Promise<void> {
722
+ await this.repo.update({ jobId }, update);
723
+ }
470
724
 
471
- 1. **Image Upload**:
472
- The controller receives a stream. `uploadService.uploadImageCore` processes it using the `CloudinaryUploadStrategy` and notifies the `LoggingObserver`.
473
-
474
- 2. **Metadata Search**:
475
- The search query is validated via `@TextField('Query', 3, 50)`.
476
-
477
- 3. **Result Caching**:
478
- Search results are cached in Redis using a Hash (`hSet`). Subsequent searches for the same term are served in under 1ms.
725
+ async findByJobId(jobId: string): Promise<NotificationLog | null> {
726
+ return this.repo.findOne({ where: { jobId } });
727
+ }
479
728
 
480
- ---
729
+ async findByRecipientId(recipientId: string): Promise<NotificationLog[]> {
730
+ return this.repo.find({ where: { recipientId }, order: { createdAt: 'DESC' } });
731
+ }
481
732
 
482
- ## Technical Appendix: The Complete API Dictionary
733
+ async findAll(filter?: Partial<NotificationLog>): Promise<NotificationLog[]> {
734
+ return this.repo.find({ where: filter as any, order: { createdAt: 'DESC' } });
735
+ }
736
+ }
737
+ ```
483
738
 
484
- This section provides an exhaustive reference for internal services. Every method is documented with its signature, parameter requirements, and a real-world example.
739
+ Register in your module:
485
740
 
486
- ### 1. `RedisService` (`@bts-soft/cache`)
741
+ ```typescript
742
+ import { NOTIFICATION_LOG_REPOSITORY } from '@bts-soft/core';
487
743
 
488
- 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.
744
+ @Module({
745
+ providers: [
746
+ { provide: NOTIFICATION_LOG_REPOSITORY, useClass: DbNotificationLogRepo },
747
+ ],
748
+ })
749
+ export class AppModule {}
750
+ ```
489
751
 
490
- #### Core Key-Value Operations
752
+ ### User Opt-Out Management
491
753
 
492
- | Method | Signature | Description | Example |
493
- | :--- | :--- | :--- | :--- |
494
- | `set` | `(key, value, ttl?)` | Stores any JS object or primitive. | `await set('k', {a:1}, 60)` |
495
- | `get` | `<T>(key)` | Retrieves and JSON-parses value. | `await get<User>('u1')` |
496
- | `del` | `(key)` | Deletes a key. | `await del('old_key')` |
497
- | `mSet` | `(data)` | Multi-set via pipeline. | `await mSet({a:1, b:2})` |
498
- | `mGet` | `(keys)` | Multi-get. | `await mGet(['a', 'b'])` |
499
- | `exists`| `(key)` | Checks key existence. | `await exists('token')` |
500
- | `expire`| `(key, sec)` | Updates TTL. | `await expire('k', 3600)` |
501
- | `ttl` | `(key)` | Gets remaining seconds. | `await ttl('k')` |
502
-
503
- #### Atomic Counters & Numeric Groups
504
-
505
- | Method | Signature | Description | Example |
506
- | :--- | :--- | :--- | :--- |
507
- | `incr` | `(key)` | Increments by 1. | `await incr('views')` |
508
- | `incrBy` | `(key, n)` | Increments by integer N. | `await incrBy('score', 10)` |
509
- | `decr` | `(key)` | Decrements by 1. | `await decr('retries')` |
510
- | `decrBy` | `(key, n)` | Decrements by integer N. | `await decrBy('balance', 5)` |
511
- | `getSet` | `(key, val)` | Set new, return old. | `await getSet('v', 5)` |
754
+ ```typescript
755
+ import { Inject, Injectable } from '@nestjs/common';
756
+ import { USER_PREFERENCE_REPOSITORY, IUserPreferenceRepository } from '@bts-soft/core';
512
757
 
513
- #### Hash Operations (Field-Level Access)
758
+ @Injectable()
759
+ export class PreferenceService {
760
+ constructor(
761
+ @Inject(USER_PREFERENCE_REPOSITORY)
762
+ private readonly prefs: IUserPreferenceRepository,
763
+ ) {}
514
764
 
515
- | Method | Signature | Description | Example |
516
- | :--- | :--- | :--- | :--- |
517
- | `hSet` | `(k, f, v)` | Sets field in hash. | `await hSet('u:1', 'n', 'O')` |
518
- | `hGet` | `<T>(k, f)` | Gets field value. | `await hGet<string>('u:1', 'n')`|
519
- | `hGetAll`| `(k)` | Gets all fields as Object. | `await hGetAll('u:1')` |
520
- | `hDel` | `(k, f)` | Deletes field. | `await hDel('u:1', 'n')` |
521
- | `hKeys` | `(k)` | Returns all field names. | `await hKeys('u:1')` |
522
- | `hVals` | `(k)` | Returns all field values. | `await hVals('u:1')` |
523
- | `hLen` | `(k)` | Field count. | `await hLen('u:1')` |
524
- | `hIncrBy`| `(k, f, n)` | Incr field by N. | `await hIncrBy('u:1', 'v', 1)`|
525
-
526
- #### Set Operations (Uniqueness)
527
-
528
- | Method | Signature | Description | Example |
529
- | :--- | :--- | :--- | :--- |
530
- | `sAdd` | `(k, ...m)` | Adds members. | `await sAdd('tags', 'a', 'b')` |
531
- | `sRem` | `(k, ...m)` | Removes members. | `await sRem('tags', 'a')` |
532
- | `sMembers`| `(k)` | Returns all members. | `await sMembers('tags')` |
533
- | `sCard` | `(k)` | Set count. | `await sCard('tags')` |
534
- | `sIsMember`| `(k, m)` | Membership check. | `await sIsMember('tags', 'a')`|
535
- | `sInter` | `(...k)` | Set intersection. | `await sInter('s1', 's2')` |
536
- | `sUnion` | `(...k)` | Set union. | `await sUnion('s1', 's2')` |
765
+ optOut(userId: string, channel: string) {
766
+ return this.prefs.setOptOut(userId, channel, true);
767
+ }
537
768
 
538
- #### Sorted Sets (Rankings)
769
+ optIn(userId: string, channel: string) {
770
+ return this.prefs.setOptOut(userId, channel, false);
771
+ }
772
+ }
773
+ ```
539
774
 
540
- | Method | Signature | Description | Example |
541
- | :--- | :--- | :--- | :--- |
542
- | `zAdd` | `(k, s, m)` | Add item with score. | `await zAdd('lb', 100, 'u1')` |
543
- | `zRange` | `(k, s, t)` | Range by index. | `await zRange('lb', 0, 10)` |
544
- | `zRank` | `(k, m)` | Member rank. | `await zRank('lb', 'u1')` |
545
- | `zScore` | `(k, m)` | Member score. | `await zScore('lb', 'u1')` |
546
- | `zRem` | `(k, m)` | Remove member. | `await zRem('lb', 'u1')` |
775
+ ### BullMQ Retry Policy
547
776
 
548
- #### Geospatial Operations
777
+ | Setting | Value |
778
+ | :--- | :--- |
779
+ | Max attempts | 3 |
780
+ | Strategy | Exponential backoff |
781
+ | Initial delay | 5,000ms |
782
+ | Progression | 5s -> 10s -> 20s |
549
783
 
550
- | Method | Signature | Description | Example |
551
- | :--- | :--- | :--- | :--- |
552
- | `geoAdd` | `(k, lo, la, m)` | Store coordinate. | `await geoAdd('idx', 31, 30, 'P')`|
553
- | `geoDist` | `(m1, m2, u)` | Distance calculation. | `await geoDist('P1', 'P2', 'km')`|
554
- | `geoPos` | `(k, m)` | Get Lat/Long. | `await geoPos('idx', 'P')` |
784
+ Client errors (4xx) are not retried. Only provider errors (5xx, connection timeouts) trigger retry logic.
555
785
 
556
- #### HyperLogLog (Approximate Counting)
786
+ ---
557
787
 
558
- | Method | Signature | Description | Example |
559
- | :--- | :--- | :--- | :--- |
560
- | `pfAdd` | `(k, ...e)` | Add elements. | `await pfAdd('uv', '1', '2')` |
561
- | `pfCount` | `(...k)` | Get cardinality. | `await pfCount('uv')` |
788
+ ## Package: @bts-soft/upload
562
789
 
563
- ---
790
+ [![npm](https://img.shields.io/npm/v/@bts-soft/upload?style=flat-square&color=CB3837&logo=npm)](https://www.npmjs.com/package/@bts-soft/upload)
564
791
 
565
- ## Security Audit & Hardening Guide
792
+ A production-grade media orchestration library supporting single-file uploads, large-file chunked uploads with resume capability, real-time Server-Sent Events progress streaming, and multi-provider storage backends.
566
793
 
567
- The `@bts-soft/core` package is built with a "Zero Trust" mindset toward external input. Here is how we enforce security across different layers.
794
+ ### System Architecture
568
795
 
569
- ### 1. Database Security (SQL Injection)
796
+ ```
797
+ Client Request
798
+ │
799
+ ▼
800
+ RateLimiterService (Token Bucket)
801
+ ├── [rate limited] ──► 429 Too Many Requests
802
+ │
803
+ ▼
804
+ FileValidatorService (extension + size check)
805
+ ├── [invalid] ──► 400 Bad Request
806
+ │
807
+ ▼
808
+ Upload Controllers (REST / GraphQL)
809
+ │
810
+ ├── [single file] ──► UploadService
811
+ │ │
812
+ │ Upload Commands
813
+ │ │
814
+ │ Strategy Pattern
815
+ │ ├── LocalDiskUploadStrategy
816
+ │ └── CloudinaryUploadStrategy
817
+ │
818
+ └── [large file] ──► ChunkedUploadService
819
+ │
820
+ LocalChunkStorage
821
+ │ [final chunk received]
822
+ ▼
823
+ UploadQueueService ──► BullMQ Queue
824
+ │
825
+ UploadProcessor
826
+ │
827
+ Merge all chunks
828
+ │
829
+ ──► Upload Commands (same path)
830
+ │ (progress updates)
831
+ UploadJobService
832
+ │
833
+ SSE Controller ──► Client
834
+ ```
570
835
 
571
- We use a two-tier defense system against SQL injection:
836
+ ### Design Patterns
572
837
 
573
- * **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.
574
- * **Infrastructure Level**: The `SqlInjectionInterceptor` provides a global safety net by scanning every request property.
838
+ **Strategy Pattern:** The `IUploadStrategy` interface decouples file storage from the application. Switching between Local Disk and Cloudinary requires only an environment variable change.
575
839
 
576
- ### 2. Output Sanitization (XSS)
840
+ ```
841
+ UploadService
842
+ │
843
+ ▼
844
+ IUploadStrategy (interface)
845
+ │
846
+ ├──► LocalDiskUploadStrategy (UPLOAD_PROVIDER=local)
847
+ │
848
+ └──► CloudinaryUploadStrategy (UPLOAD_PROVIDER=cloudinary)
849
+ ```
577
850
 
578
- The `ClassSerializerInterceptor` is mandatory. It ensures that:
579
- - Sensitive internal data (like user passwords or internal DB IDs) are stripped from the response.
580
- - Only fields explicitly marked with `@Expose()` are sent to the client.
851
+ **Command Pattern:** Each media type (image, video, audio, raw file, 3D model) is encapsulated in its own command object carrying type-specific processing logic such as chunked video upload or image optimization.
581
852
 
582
- ### 3. Rate Limiting (Brute Force)
853
+ **Observer Pattern:** `IUploadObserver` implementations register to receive lifecycle events for auditing, analytics, or cleanup pipelines.
583
854
 
584
- By using the `ThrottlingModule`, you can define per-endpoint limits based on the IP or the User ID.
855
+ ### Setup
585
856
 
586
857
  ```typescript
587
- @Throttle({ default: { limit: 10, ttl: 60000 } })
588
- @Post('login')
589
- async login() { ... }
858
+ import { UploadModule } from '@bts-soft/core';
859
+
860
+ @Module({ imports: [UploadModule] })
861
+ export class AppModule {}
590
862
  ```
591
863
 
592
- ### 4. Media Safety
864
+ ### Media Specifications
593
865
 
594
- The Upload module provides:
595
- - Extension white-listing to prevent uploading executable files (like `.exe`, `.sh`).
596
- - Size caps to prevent Disk Exhaustion attacks.
597
- - Strategy-level validation to ensure the cloud provider is reputable and secure.
866
+ | Type | Extensions | Default Size Limit | Processing |
867
+ | :--- | :--- | :--- | :--- |
868
+ | Images | `jpg`, `png`, `webp`, `gif` | 5 MB | Auto-optimization, `fetch_format: auto` |
869
+ | Videos | `mp4`, `webm`, `avi`, `mov` | 100 MB | Chunked upload (6MB chunks), duration extraction |
870
+ | Audio | `mp3`, `wav`, `ogg`, `m4a` | 50 MB | Waveform generation via Cloudinary video resource type |
871
+ | Raw Files | `pdf`, `doc`, `docx`, `zip`, `txt` | 10 MB | Stored as raw binary with original headers |
872
+ | 3D Models | `glb`, `gltf`, `fbx`, `obj`, `stl` | 100 MB | Binary model storage |
598
873
 
599
- ### 5. Production Optimization & Information Leakage Prevention
874
+ ### API Reference
600
875
 
601
- 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"`.
876
+ **UploadService:**
602
877
 
603
- If your production environment requires structured logging, you should use the NestJS `Logger` class or a dedicated logging transport instead of native `console` methods.
878
+ ```typescript
879
+ uploadImageCore(file: UploadStreamInput): Promise<UploadResult>
880
+ uploadVideoCore(file: UploadStreamInput): Promise<UploadResult>
881
+ uploadAudioCore(file: UploadStreamInput): Promise<UploadResult>
882
+ uploadFileCore(file: UploadStreamInput): Promise<UploadResult>
883
+ uploadModel3dCore(file: UploadStreamInput): Promise<UploadResult>
884
+ deleteImage(url: string): Promise<boolean>
885
+ deleteVideo(url: string): Promise<boolean>
886
+ deleteAudio(url: string): Promise<boolean>
887
+ deleteFile(url: string): Promise<boolean>
888
+ ```
604
889
 
605
- ---
890
+ **ChunkedUploadService:**
606
891
 
607
- ## Deployment, CI/CD, and Operations
892
+ ```typescript
893
+ initiateUpload(filename, size, type, fileHash?, userId?): Promise<{ jobId: string, status: string }>
894
+ uploadChunk(jobId, chunkIndex, totalChunks, buffer, fileHash?, userId?): Promise<{ progress: number, completed: boolean }>
895
+ getUploadedChunks(jobId): Promise<number[]>
896
+ ```
608
897
 
609
- A production-ready application requires a production-ready infrastructure.
898
+ ### Chunked Upload Implementation
610
899
 
611
- ### Environment Variable Reference
900
+ ```typescript
901
+ // Backend REST controller
902
+ @Controller('upload')
903
+ export class UploadController {
904
+ constructor(private readonly chunked: ChunkedUploadService) {}
905
+
906
+ @Post('chunk/initiate')
907
+ initiateUpload(@Body() body: { filename: string; size: number; type: string }) {
908
+ return this.chunked.initiateUpload(body.filename, body.size, body.type);
909
+ }
612
910
 
613
- Ensure the following variables are defined in your `.env` or CI secrets:
911
+ @Post('chunk/upload')
912
+ @UseInterceptors(FileInterceptor('file'))
913
+ uploadChunk(
914
+ @Body() body: { jobId: string; chunkIndex: string; totalChunks: string },
915
+ @UploadedFile() file: Express.Multer.File,
916
+ ) {
917
+ return this.chunked.uploadChunk(
918
+ body.jobId,
919
+ parseInt(body.chunkIndex, 10),
920
+ parseInt(body.totalChunks, 10),
921
+ file.buffer,
922
+ );
923
+ }
614
924
 
615
- #### General
616
- - `NODE_ENV`: `production` | `development` | `test`
617
- - `PORT`: Default 3000
925
+ @Get('chunk/uploaded/:jobId')
926
+ getUploadedChunks(@Param('jobId') jobId: string) {
927
+ return this.chunked.getUploadedChunks(jobId);
928
+ }
929
+ }
930
+ ```
618
931
 
619
- #### Redis Configuration
620
- - `REDIS_HOST`: e.g., `localhost`
621
- - `REDIS_PORT`: Default `6379`
622
- - `REDIS_PASSWORD`: Optional
932
+ Frontend with resume support:
623
933
 
624
- #### Notifications Configuration
625
- - `EMAIL_HOST`, `EMAIL_PORT`, `EMAIL_USER`, `EMAIL_PASS`, `EMAIL_SENDER`
626
- - `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, `TWILIO_SMS_NUMBER`, `TWILIO_WHATSAPP_NUMBER`
627
- - `TELEGRAM_BOT_TOKEN`
628
- - `FIREBASE_SERVICE_ACCOUNT_PATH`
934
+ ```typescript
935
+ async function uploadFileInChunks(file: File, userId: string) {
936
+ const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB per chunk
937
+ const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
938
+
939
+ const { jobId } = await fetch('/upload/chunk/initiate', {
940
+ method: 'POST',
941
+ headers: { 'Content-Type': 'application/json' },
942
+ body: JSON.stringify({ filename: file.name, size: file.size, type: 'video', userId }),
943
+ }).then(r => r.json());
944
+
945
+ // Resume: get already uploaded chunk indexes
946
+ const uploadedChunks: number[] = await fetch(`/upload/chunk/uploaded/${jobId}`).then(r => r.json());
947
+
948
+ // Subscribe to SSE progress stream
949
+ const es = new EventSource(`/upload/jobs/${jobId}/stream`);
950
+ es.onmessage = ({ data }) => {
951
+ const { type, progress, url } = JSON.parse(data);
952
+ if (type === 'completed') { console.log('Final URL:', url); es.close(); }
953
+ if (type === 'progress') { console.log(`${progress}%`); }
954
+ };
629
955
 
630
- #### Media Configuration
631
- - `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, `CLOUDINARY_API_SECRET`
956
+ for (let i = 0; i < totalChunks; i++) {
957
+ if (uploadedChunks.includes(i)) continue; // Skip already uploaded chunks
632
958
 
633
- ---
959
+ const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
960
+ const form = new FormData();
961
+ form.append('jobId', jobId);
962
+ form.append('chunkIndex', String(i));
963
+ form.append('totalChunks', String(totalChunks));
964
+ form.append('file', chunk);
634
965
 
635
- ## The Giant Book of Usage Examples
966
+ await fetch('/upload/chunk/upload', { method: 'POST', body: form });
967
+ }
968
+ }
969
+ ```
636
970
 
637
- This section provides complete, copy-pasteable implementations of every core feature for both REST and GraphQL architectures.
971
+ ### Custom Observer
638
972
 
639
- ---
973
+ ```typescript
974
+ import { Injectable, OnModuleInit } from '@nestjs/common';
975
+ import { IUploadObserver, UploadObserverManager } from '@bts-soft/core';
640
976
 
641
- ### 1. Advanced Cache Patterns
977
+ @Injectable()
978
+ export class AuditObserver implements IUploadObserver, OnModuleInit {
979
+ constructor(private readonly manager: UploadObserverManager) {}
642
980
 
643
- #### Pattern: "Cache-Aside" for High Traffic Entities
644
- Best for User Profiles, Product Details, or Settings.
981
+ onModuleInit() { this.manager.register(this); }
645
982
 
646
- **REST Controller Implementation:**
647
- ```typescript
648
- @Get(':id')
649
- async getProfile(@Param('id') id: string) {
650
- const cacheKey = `user:profile:${id}`;
651
-
652
- // 1. Try to get from Cache
653
- const cachedUser = await this.redis.get<User>(cacheKey);
654
- if (cachedUser) return cachedUser;
655
-
656
- // 2. Fetch from DB if not in Cache
657
- const user = await this.userRepo.findOneBy({ id });
658
- if (!user) throw new NotFoundException();
659
-
660
- // 3. Store in Cache with 10 min TTL
661
- await this.redis.set(cacheKey, user, 600);
662
-
663
- return user;
664
- }
665
- ```
983
+ async onUploadSuccess(url: string, type: string) {
984
+ console.log(`Uploaded: ${url} (type: ${type})`);
985
+ }
666
986
 
667
- **GraphQL Resolver Implementation:**
668
- ```typescript
669
- @Query(() => User)
670
- async userProfile(@Args('id') id: string) {
671
- const cacheKey = `user:profile:${id}`;
672
-
673
- return await this.redis.getOrSet(cacheKey, async () => {
674
- return await this.userRepo.findOneBy({ id });
675
- }, 600);
676
- }
677
- ```
987
+ async onUploadFail(error: Error) {
988
+ console.error(`Upload failed: ${error.message}`);
989
+ }
678
990
 
679
- #### Pattern: "Atomic Rate Limiter" (Custom Logic)
680
- When the standard Throttler isn't enough.
991
+ async onDeleteSuccess(url: string) {
992
+ console.log(`Deleted: ${url}`);
993
+ }
681
994
 
682
- ```typescript
683
- async isAllowed(userId: string, action: string): Promise<boolean> {
684
- const key = `limit:${userId}:${action}`;
685
- const count = await this.redis.incr(key);
686
-
687
- if (count === 1) {
688
- await this.redis.expire(key, 60); // Set 1 min window on first attempt
995
+ async onDeleteFail(error: Error, url: string) {
996
+ console.error(`Delete failed for ${url}: ${error.message}`);
689
997
  }
690
-
691
- return count <= 5; // Allow 5 actions per minute
692
998
  }
693
999
  ```
694
1000
 
695
1001
  ---
696
1002
 
697
- ### 2. Multi-Channel Notification Orchestration
1003
+ ## Package: @bts-soft/common
698
1004
 
699
- #### Pattern: "The Preference-Aware Broadcaster"
700
- Sends notifications based on user opt-in channels.
1005
+ [![npm](https://img.shields.io/npm/v/@bts-soft/common?style=flat-square&color=CB3837&logo=npm)](https://www.npmjs.com/package/@bts-soft/common)
701
1006
 
702
- ```typescript
703
- async notifyUser(user: User, payload: any) {
704
- const jobs = [];
705
-
706
- if (user.wantsEmail) {
707
- jobs.push(this.notif.send(ChannelType.EMAIL, {
708
- recipientId: user.email,
709
- ...payload
710
- }));
711
- }
1007
+ The standard library of the BTS Soft ecosystem. Provides the foundation shared by all services: interceptors, exception filters, base entities, ORM adapters, ID generation, infrastructure modules, distributed locking, and idempotency.
712
1008
 
713
- if (user.wantsSms) {
714
- jobs.push(this.notif.send(ChannelType.SMS, {
715
- recipientId: user.phone,
716
- ...payload
717
- }));
718
- }
1009
+ ### Request Processing Architecture
719
1010
 
720
- await Promise.all(jobs);
721
- }
1011
+ ```
1012
+ Client Request
1013
+ |
1014
+ v
1015
+ CommonThrottlerGuard
1016
+ |
1017
+ v
1018
+ SqlInjectionInterceptor
1019
+ |
1020
+ v
1021
+ IdempotencyInterceptor
1022
+ |
1023
+ v
1024
+ Guards / Authentication
1025
+ |
1026
+ v
1027
+ Controller / Resolver --[exception]--> RestExceptionFilter / GqlHttpExceptionFilter
1028
+ |
1029
+ v
1030
+ GeneralResponseInterceptor Standard Error Envelope
1031
+ |
1032
+ v
1033
+ Unified JSON Envelope --> Client
722
1034
  ```
723
1035
 
724
- ---
1036
+ ### Global Interceptors
725
1037
 
726
- ### 3. Media Handling Lifecycle
1038
+ Call `setupInterceptors(app)` in `main.ts` to activate three interceptors globally in this order:
727
1039
 
728
- #### Pattern: "Avatar Upload with Auto-Cleanup"
729
- Uploads a new avatar and deletes the old one from Cloudinary.
1040
+ **1. ClassSerializerInterceptor**
1041
+ Applies `class-transformer` serialization rules. Fields decorated with `@Exclude()` are stripped from responses. Fields decorated with `@Expose()` are included. This prevents sensitive data such as passwords and internal database IDs from leaking to clients.
730
1042
 
731
- ```typescript
732
- async updateAvatar(userId: string, file: UploadFile) {
733
- const user = await this.userRepo.findOneBy({ id: userId });
734
-
735
- // 1. Upload new image
736
- const result = await this.upload.uploadImageCore(file, 'avatars');
737
-
738
- // 2. Delete old image if exists
739
- if (user.avatarUrl) {
740
- const publicId = this.getPublicIdFromUrl(user.avatarUrl);
741
- await this.upload.deleteImage(publicId);
1043
+ **2. SqlInjectionInterceptor**
1044
+ Recursively scans all incoming request body fields, query string parameters, and path parameters. Any string matching the SQL injection regex causes an immediate `400 Bad Request` before the controller executes. Endpoints processing trusted raw SQL can opt out with `@SkipSqlCheck()`.
1045
+
1046
+ **3. GeneralResponseInterceptor**
1047
+ Wraps every controller return value in the standard response envelope:
1048
+
1049
+ ```json
1050
+ {
1051
+ "success": true,
1052
+ "statusCode": 200,
1053
+ "message": "Operation executed successfully",
1054
+ "timeStamp": "2025-01-15T10:00:00.000Z",
1055
+ "data": {},
1056
+ "items": [],
1057
+ "pagination": {
1058
+ "total": 100,
1059
+ "page": 1,
1060
+ "limit": 10
742
1061
  }
743
-
744
- // 3. Update DB
745
- user.avatarUrl = result.url;
746
- await this.userRepo.save(user);
747
1062
  }
748
1063
  ```
749
1064
 
750
- ---
751
-
752
- ## Frequently Asked Questions (FAQ)
1065
+ GraphQL contexts are detected and bypass HTTP envelope wrapping to preserve Apollo schema integrity.
753
1066
 
754
- ### General
1067
+ ### Base Entities
755
1068
 
756
- **Q1: Can I use @bts-soft/core with NestJS 10?**
757
- A: No, version 2.2.4+ requires NestJS 11 due to dependency alignment and peer dependency overrides.
1069
+ #### AgnosticEntity
758
1070
 
759
- **Q2: Does this package include TypeORM?**
760
- 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.
1071
+ Pure TypeScript base, no ORM decorators. Generates IDs using `IdGenerator` and maintains `createdAt` and `updatedAt` timestamps.
761
1072
 
762
- ---
1073
+ #### ORM-Specific Adapters
763
1074
 
764
- ### Validation
1075
+ | Sub-path | Class | ORM |
1076
+ | :--- | :--- | :--- |
1077
+ | `@bts-soft/common/typeorm` | `TypeOrmBaseEntity` | TypeORM with lifecycle hooks (`@AfterInsert`, `@AfterUpdate`, `@BeforeRemove`) |
1078
+ | `@bts-soft/common/mongoose` | `MongooseBaseEntity` | Mongoose with `@Schema({ timestamps: true })` |
1079
+ | `@bts-soft/common/sequelize` | `SequelizeBaseEntity` | Sequelize with `@Table({ timestamps: true })` |
1080
+ | `@bts-soft/common/prisma` | `PrismaBase` | Helper `generateId(strategy?)` and `IPrismaEntity` interface |
765
1081
 
766
- **Q3: How do I disable SQL injection checks for a specific field?**
767
- A: You should use the standard `class-validator` decorators (like `@IsString()`) instead of the `@TextField` composite decorators.
1082
+ ### ID Generation Strategy
768
1083
 
769
- **Q4: Can I add custom transformation logic to @PhoneField?**
770
- A: No, the PhoneField uses a non-configurable regex cleaner. If you need custom cleaning, use `Transform()` manually.
1084
+ ```
1085
+ Entity / ORM Base
1086
+ │
1087
+ ▼
1088
+ IdGenerator.generate(strategy?)
1089
+ │
1090
+ ├── ULID ──► 128-bit, lexicographically sortable (default)
1091
+ ├── UUID v4 ──► RFC4122 fully random 128-bit
1092
+ ├── Snowflake ──► 64-bit: timestamp + worker ID + sequence counter
1093
+ └── CUID2 ──► sortable, collision-resistant, URL-safe
1094
+ ```
771
1095
 
772
- ---
1096
+ | Strategy | Characteristics | Best For |
1097
+ | :--- | :--- | :--- |
1098
+ | **ULID** (default) | 48-bit timestamp + 80-bit random, B-Tree friendly | High-write databases such as PostgreSQL and MySQL |
1099
+ | **UUID v4** | Fully random 128-bit | Compatibility with existing systems |
1100
+ | **Snowflake** | 64-bit, embeds worker ID (0-1023) and sequence (0-4095) | Distributed microservice node coordination |
1101
+ | **CUID2** | Collision-resistant, URL-safe | Short IDs in URLs and external references |
773
1102
 
774
- ### Cache
1103
+ ```typescript
1104
+ import { IdGenerator } from '@bts-soft/core';
775
1105
 
776
- **Q5: What happens if the Redis server goes down?**
777
- 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.
1106
+ IdGenerator.setDefaultStrategy('snowflake');
1107
+ IdGenerator.setWorkerId(12); // Required for distributed deployments
778
1108
 
779
- ---
1109
+ const id = IdGenerator.generate(); // Uses default strategy
1110
+ const ulid = IdGenerator.generate('ulid'); // Override per call
1111
+ ```
780
1112
 
781
- ## Exhaustive Redis API Guide
1113
+ ### Infrastructure Modules
782
1114
 
783
- 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.
1115
+ #### ThrottlingModule
784
1116
 
785
- ---
1117
+ Rate limiting for REST and GraphQL using sliding window counters. Supports tiered limits:
786
1118
 
787
- ### Basic Key-Value Operations
788
-
789
- #### 1. `set`
790
- **Description:** Stores a value in Redis with automatic serialization of objects and primitives.
791
- **Interface:** `set(key: string, value: any, ttl: number = 3600): Promise<void>`
792
- **Parameters:**
793
- - `key`: The unique identifier in Redis.
794
- - `value`: The data to store. Can be a string, number, or plain JavaScript object.
795
- - `ttl`: Time-To-Live in seconds. Default is 1 hour.
796
- **Return:** `Promise<void>`
797
- **Examples:**
798
1119
  ```typescript
799
- // REST Example
800
- await this.redisService.set(`user:${id}`, userData, 3600);
1120
+ import { ThrottlingModule } from '@bts-soft/core';
801
1121
 
802
- // GraphQL Example
803
- await this.redisService.set(`profile:${userId}`, profileData, 1800);
1122
+ @Module({
1123
+ imports: [
1124
+ ThrottlingModule.forRoot([
1125
+ { name: 'short', ttl: 1_000, limit: 10 },
1126
+ { name: 'medium', ttl: 10_000, limit: 50 },
1127
+ { name: 'long', ttl: 60_000, limit: 250 },
1128
+ ]),
1129
+ ],
1130
+ })
1131
+ export class AppModule {}
804
1132
  ```
805
1133
 
806
- #### 2. `get`
807
- **Description:** Retrieves and deserializes a value from Redis.
808
- **Interface:** `get<T = any>(key: string): Promise<T | null>`
809
- **Parameters:**
810
- - `key`: The key to retrieve.
811
- **Return:** `Promise<T | null>`. Returns `null` if the key does not exist.
812
- **Examples:**
813
- ```typescript
814
- // REST Example
815
- const user = await this.redisService.get<UserEntity>(`user:${id}`);
1134
+ #### GraphqlModule
816
1135
 
817
- // GraphQL Example
818
- const profile = await this.redisService.get<ProfileInput>(`profile:${userId}`);
819
- ```
1136
+ Apollo Server with WebSocket subscriptions, GraphQL Upload, CSRF prevention, and Apollo Federation:
820
1137
 
821
- #### 3. `del`
822
- **Description:** Deletes one or more keys from Redis.
823
- **Interface:** `del(...keys: string[]): Promise<void>`
824
- **Parameters:**
825
- - `keys`: One or more keys to delete.
826
- **Return:** `Promise<void>`
827
- **Example:**
828
1138
  ```typescript
829
- await this.redisService.del(`user:${id}`, `session:${token}`);
830
- ```
831
-
832
- #### 4. `exists`
833
- **Description:** Checks if a key exists in Redis.
834
- **Interface:** `exists(key: string): Promise<boolean>`
835
- **Return:** `true` if it exists, `false` otherwise.
836
-
837
- #### 5. `expire`
838
- **Description:** Sets or updates the TTL of a key.
839
- **Interface:** `expire(key: string, seconds: number): Promise<boolean>`
840
-
841
- #### 6. `ttl`
842
- **Description:** Gets the remaining TTL of a key.
843
- **Interface:** `ttl(key: string): Promise<number>`
844
-
845
- ---
846
-
847
- ### String & Numeric Operations
848
-
849
- #### 7. `incr`
850
- **Description:** Atomically increments the numeric value of a key.
851
- **Interface:** `incr(key: string): Promise<number>`
852
- **Usage:** `const newCount = await this.redisService.incr('hit_counter');`
853
-
854
- #### 8. `incrBy`
855
- **Description:** Increments a key by a specific integer.
856
- **Interface:** `incrBy(key: string, increment: number): Promise<number>`
857
-
858
- #### 9. `incrByFloat`
859
- **Description:** Increments a key by a floating-point number.
860
- **Interface:** `incrByFloat(key: string, increment: number): Promise<number>`
861
-
862
- #### 10. `decr`
863
- **Description:** Atomically decrements a key.
864
- **Interface:** `decr(key: string): Promise<number>`
865
-
866
- #### 11. `getSet`
867
- **Description:** Sets a new value and returns the old one. This is an atomic operation.
868
- **Interface:** `getSet(key: string, value: any): Promise<string | null>`
869
-
870
- #### 12. `strlen`
871
- **Description:** Returns the length of a string value.
872
- **Interface:** `strlen(key: string): Promise<number>`
873
-
874
- ---
875
-
876
- ### Hash Operations (Object Manipulation)
877
-
878
- #### 13. `hSet`
879
- **Description:** Sets a field in a Redis hash.
880
- **Interface:** `hSet(key: string, field: string, value: any): Promise<void>`
881
-
882
- #### 14. `hGet`
883
- **Description:** Gets a field from a Redis hash.
884
- **Interface:** `hGet<T = any>(key: string, field: string): Promise<T | null>`
885
-
886
- #### 15. `hGetAll`
887
- **Description:** Gets all fields and values from a Redis hash as a JavaScript object.
888
- **Interface:** `hGetAll(key: string): Promise<Record<string, any>>`
889
-
890
- #### 16. `hDel`
891
- **Description:** Deletes fields from a hash.
892
- **Interface:** `hDel(key: string, ...fields: string[]): Promise<void>`
893
-
894
- #### 17. `hKeys`
895
- **Description:** Gets all field names in a hash.
896
- **Interface:** `hKeys(key: string): Promise<string[]>`
897
-
898
- #### 18. `hVals`
899
- **Description:** Gets all field values in a hash.
900
- **Interface:** `hVals(key: string): Promise<any[]>`
901
-
902
- #### 19. `hLen`
903
- **Description:** Gets the number of fields in a hash.
904
- **Interface:** `hLen(key: string): Promise<number>`
905
-
906
- #### 20. `hIncrBy`
907
- **Description:** Increments a numeric hash field.
908
- **Interface:** `hIncrBy(key: string, field: string, increment: number): Promise<number>`
909
-
910
- ---
911
-
912
- ### Set Operations (Unique Collections)
913
-
914
- #### 21. `sAdd`
915
- **Description:** Adds one or more members to a set.
916
- **Interface:** `sAdd(key: string, ...members: any[]): Promise<void>`
917
-
918
- #### 22. `sRem`
919
- **Description:** Removes members from a set.
920
- **Interface:** `sRem(key: string, ...members: any[]): Promise<void>`
921
-
922
- #### 23. `sMembers`
923
- **Description:** Gets all members of a set.
924
- **Interface:** `sMembers(key: string): Promise<any[]>`
925
-
926
- #### 24. `sIsMember`
927
- **Description:** Checks if a value is a member of a set.
928
- **Interface:** `sIsMember(key: string, member: any): Promise<boolean>`
929
-
930
- #### 25. `sCard`
931
- **Description:** Gets the number of members in a set.
932
- **Interface:** `sCard(key: string): Promise<number>`
933
-
934
- #### 26. `sInter`
935
- **Description:** Finds the intersection of multiple sets.
936
- **Interface:** `sInter(...keys: string[]): Promise<any[]>`
937
-
938
- ---
939
-
940
- #### 27. `sUnion`
941
- **Description:** Returns the union of multiple sets.
942
- **Interface:** `sUnion(...keys: string[]): Promise<any[]>`
943
-
944
- #### 28. `sDiff`
945
- **Description:** Returns the difference between the first set and all successive sets.
946
- **Interface:** `sDiff(...keys: string[]): Promise<any[]>`
947
-
948
- ---
949
-
950
- ### Sorted Set Operations (Leaderboards & Priority)
951
-
952
- #### 29. `zAdd`
953
- **Description:** Adds a member with a specific score to a sorted set.
954
- **Interface:** `zAdd(key: string, score: number, member: any): Promise<void>`
955
-
956
- #### 30. `zRange`
957
- **Description:** Returns a range of members by their index.
958
- **Interface:** `zRange(key: string, start: number, stop: number): Promise<any[]>`
959
-
960
- #### 31. `zRevRange`
961
- **Description:** Returns a range of members, ordered from high to low score.
962
- **Interface:** `zRevRange(key: string, start: number, stop: number): Promise<any[]>`
963
-
964
- #### 32. `zRank`
965
- **Description:** Returns the rank (index) of a member, sorted by score.
966
- **Interface:** `zRank(key: string, member: any): Promise<number | null>`
967
-
968
- #### 33. `zScore`
969
- **Description:** Returns the score associated with a member.
970
- **Interface:** `zScore(key: string, member: any): Promise<number | null>`
971
-
972
- #### 34. `zCard`
973
- **Description:** Gets the number of elements in a sorted set.
974
- **Interface:** `zCard(key: string): Promise<number>`
975
-
976
- #### 35. `zCount`
977
- **Description:** Counts members with scores within a specific range.
978
- **Interface:** `zCount(key: string, min: number, max: number): Promise<number>`
979
-
980
- #### 36. `zRem`
981
- **Description:** Removes one or more members.
982
- **Interface:** `zRem(key: string, ...members: any[]): Promise<void>`
983
-
984
- ---
985
-
986
- ### List Operations (Queues & Stacks)
987
-
988
- #### 37. `lPush`
989
- **Description:** Prepends a value to a list.
990
- **Interface:** `lPush(key: string, ...values: any[]): Promise<number>`
991
-
992
- #### 38. `rPush`
993
- **Description:** Appends a value to a list.
994
- **Interface:** `rPush(key: string, ...values: any[]): Promise<number>`
995
-
996
- #### 39. `lPop`
997
- **Description:** Removes and returns the first element.
998
- **Interface:** `lPop<T = any>(key: string): Promise<T | null>`
999
-
1000
- #### 40. `rPop`
1001
- **Description:** Removes and returns the last element.
1002
- **Interface:** `rPop<T = any>(key: string): Promise<T | null>`
1003
-
1004
- #### 41. `lRange`
1005
- **Description:** Returns a range of elements from a list.
1006
- **Interface:** `lRange(key: string, start: number, stop: number): Promise<any[]>`
1007
-
1008
- #### 42. `lLen`
1009
- **Description:** Returns the length of a list.
1010
- **Interface:** `lLen(key: string): Promise<number>`
1011
-
1012
- #### 43. `lTrim`
1013
- **Description:** Trims a list to a specific range (Atomic capping).
1014
- **Interface:** `lTrim(key: string, start: number, stop: number): Promise<void>`
1015
-
1016
- ---
1017
-
1018
- ### Advanced Tooling: Pub/Sub & Locking
1019
-
1020
- #### 44. `publish`
1021
- **Description:** Posts a message to a channel.
1022
- **Interface:** `publish(channel: string, message: any): Promise<void>`
1023
-
1024
- #### 45. `subscribe`
1025
- **Description:** Listens for messages on a channel.
1026
- **Interface:** `subscribe(channel: string, callback: (message: string) => void): Promise<void>`
1027
-
1028
- #### 46. `acquireLock`
1029
- **Description:** Attempts to acquire a distributed lock.
1030
- **Interface:** `acquireLock(resource: string, value: string, ttl: number): Promise<string | null>`
1031
-
1032
- #### 47. `releaseLock`
1033
- **Description:** Safely releases a distributed lock using Lua scripting.
1034
- **Interface:** `releaseLock(resource: string, value: string): Promise<boolean>`
1035
-
1036
- ---
1037
-
1038
- ## Notification Provider Master Reference
1039
-
1040
- This section provides a deep technical dive into every supported notification channel, including its underlying technology, payload schema, and configuration secrets.
1041
-
1042
- ---
1043
-
1044
- ### 1. Email (Nodemailer Engine)
1045
-
1046
- The email channel is built for massive scale, supporting both direct SMTP and high-volume API relays.
1047
-
1048
- #### Configuration Matrix
1049
- | Variable | Description | Example |
1050
- | :--- | :--- | :--- |
1051
- | `EMAIL_HOST` | SMTP Server | `smtp.gmail.com` |
1052
- | `EMAIL_PORT` | Connection Port | `465` (SSL) or `587` (TLS) |
1053
- | `EMAIL_USER` | Auth Login | `notifications@company.com` |
1054
- | `EMAIL_PASS` | Auth Password | `password_or_app_token` |
1055
- | `EMAIL_SERVICE`| Pre-defined service | `gmail`, `outlook`, `sendgrid` |
1056
-
1057
- #### Advanced Usage: Attachments & Inline Images
1058
- ```typescript
1059
- await notificationService.send(ChannelType.EMAIL, {
1060
- recipientId: 'boss@company.com',
1061
- subject: 'Monthly Report',
1062
- body: 'Please see the attached report.',
1063
- channelOptions: {
1064
- attachments: [
1065
- {
1066
- filename: 'report.pdf',
1067
- content: pdfBuffer,
1068
- contentType: 'application/pdf'
1069
- }
1070
- ]
1071
- }
1072
- });
1073
- ```
1074
-
1075
- ---
1076
-
1077
- ### 2. WhatsApp & SMS (Twilio Hub)
1078
-
1079
- Both channels leverage the Twilio REST API with custom normalization for Middle-Eastern phone formats.
1080
-
1081
- #### The Normalization Logic
1082
- Every phone number passed to `recipientId` is passed through a sanitizer:
1083
- 1. Trims whitespace and dashes.
1084
- 2. Handles `00` prefix by converting to `+`.
1085
- 3. Automatically detects Egyptian `01` formats and prepends `+20`.
1086
- 4. Ensures the `whatsapp:` prefix is correctly applied for the WhatsApp channel.
1087
-
1088
- #### Configuration Matrix
1089
- | Variable | Description |
1090
- | :--- | :--- |
1091
- | `TWILIO_ACCOUNT_SID` | Your unique Twilio account identifier. |
1092
- | `TWILIO_AUTH_TOKEN` | Secret token for API authentication. |
1093
- | `TWILIO_SMS_NUMBER` | The registered 10-digit Twilio phone number. |
1094
- | `TWILIO_WHATSAPP_NUMBER`| The "Sandbox" or production WhatsApp number. |
1095
-
1096
- ---
1097
-
1098
- ### 3. Telegram (Bot API integration)
1099
-
1100
- The Telegram channel is the fastest way to build real-time monitoring and alert systems.
1101
-
1102
- #### Configuration
1103
- - `TELEGRAM_BOT_TOKEN`: Obtained from `@BotFather`.
1104
-
1105
- #### Rich Formatting Support
1106
- Telegram supports `HTML` and `MarkdownV2`. You can trigger these via `channelOptions`.
1107
-
1108
- ```typescript
1109
- await notificationService.send(ChannelType.TELEGRAM, {
1110
- recipientId: '@monitoring_channel',
1111
- body: '<b>CRITICAL:</b> Database CPU is at 95%',
1112
- channelOptions: { parse_mode: 'HTML' }
1113
- });
1114
- ```
1115
-
1116
- ---
1117
-
1118
- ### 4. Firebase Cloud Messaging (FCM)
1119
-
1120
- The FCM channel is the standard for mobile and web push notifications in the BTS Soft ecosystem.
1121
-
1122
- #### Device Token Management
1123
- - `recipientId`: Must be a valid FCM device token or topic name (prefixed with `/topics/`).
1124
-
1125
- #### Rich Payloads
1126
- You can pass custom data and notification options to fine-tune the delivery.
1127
-
1128
- ```typescript
1129
- await notificationService.send(ChannelType.FIREBASE_FCM, {
1130
- recipientId: 'EXPO_TOKEN_123',
1131
- title: 'Flash Sale! ⚡',
1132
- body: 'Get 50% off for the next 4 hours.',
1133
- channelOptions: {
1134
- data: { url: '/deals/flash-sale' },
1135
- options: {
1136
- priority: 'high',
1137
- timeToLive: 14400 // 4 hours
1138
- }
1139
- }
1140
- });
1141
- ```
1142
-
1143
- ---
1144
-
1145
- ### 5. Chatbot Webhooks (Discord & Teams)
1146
-
1147
- Both channels use a simplified HTTP-based webhook architecture, perfect for server alerts and team collaboration.
1148
-
1149
- #### Discord Features
1150
- - Supports rich embeds and custom avatars per message.
1151
- - Configuration: `DISCORD_WEBHOOK_URL`.
1152
-
1153
- #### MS Teams Features
1154
- - Supports adaptive cards and actionable messages.
1155
- - Configuration: `TEAMS_WEBHOOK_URL`.
1156
-
1157
- ---
1158
-
1159
- ## Technical Appendix: Complete Redis API Reference
1160
-
1161
- This specification documents every method available in the `RedisService`, providing developers with a complete catalog of atomic data operations.
1162
-
1163
- ### 1. Key Lifecycle (O(1))
1164
-
1165
- #### `get<T>(key: string): Promise<T | null>`
1166
- - **Returns**: The parsed JSON object or raw string.
1167
- - **Error Cases**: Returns `null` if key does not exist.
1168
-
1169
- #### `set(key: string, value: any, ttl?: number): Promise<void>`
1170
- - **Params**: `ttl` defaults to 3600 seconds.
1171
- - **Serialization**: Automatic `JSON.stringify` for objects.
1172
-
1173
- #### `del(key: string): Promise<void>`
1174
- - **Purpose**: Permanent removal of a key.
1175
-
1176
- #### `exists(key: string): Promise<boolean>`
1177
- - **Returns**: `true` if key exists, `false` otherwise.
1178
-
1179
- #### `expire(key: string, seconds: number): Promise<boolean>`
1180
- - **Purpose**: Update the TTL of an existing key.
1181
-
1182
- #### `ttl(key: string): Promise<number>`
1183
- - **Returns**: Remaining seconds (-1 for infinite, -2 for not found).
1184
-
1185
- ---
1186
-
1187
- ### 2. Atomic Counters (O(1))
1188
-
1189
- #### `incr(key: string): Promise<number>`
1190
- - **Purpose**: Thread-safe increment of an integer key.
1191
-
1192
- #### `decr(key: string): Promise<number>`
1193
- - **Purpose**: Thread-safe decrement.
1194
-
1195
- #### `incrBy(key: string, value: number): Promise<number>`
1196
- - **Purpose**: Increment by a specific amount.
1197
-
1198
- #### `decrBy(key: string, value: number): Promise<number>`
1199
- - **Purpose**: Decrement by a specific amount.
1200
-
1201
- ---
1202
-
1203
- ### 3. Hash Objects (O(N) for Multiple Fields)
1204
-
1205
- #### `hSet(key: string, field: string, value: any): Promise<number>`
1206
- - **Purpose**: Store a value in a hash field.
1207
-
1208
- #### `hGet<T>(key: string, field: string): Promise<T | null>`
1209
- - **Purpose**: Retrieve value from a specific hash field.
1210
-
1211
- #### `hDel(key: string, ...fields: string[]): Promise<number>`
1212
- - **Purpose**: Remove one or more fields from a hash.
1213
-
1214
- #### `hGetAll<T>(key: string): Promise<T>`
1215
- - **Purpose**: Retrieve the entire hash object.
1216
-
1217
- #### `hKeys(key: string): Promise<string[]>`
1218
- - **Purpose**: List all field names in a hash.
1219
-
1220
- ---
1221
-
1222
- ## Technical Appendix: Global Configuration Schema (Exhaustive)
1223
-
1224
- Every environment variable that influences `@bts-soft/core` is documented below.
1225
-
1226
- | Variable Name | Purpose | Example Value |
1227
- | :--- | :--- | :--- |
1228
- | `NODE_ENV` | Runtime environment | `production` | `development` |
1229
- | `REDIS_HOST` | Cache endpoint | `127.0.0.1` |
1230
- | `REDIS_PORT` | Cache port | `6379` |
1231
- | `REDIS_PASS` | Cache password | `StrongSecret123!` |
1232
- | `EMAIL_SERVICE` | Nodemailer provider | `gmail` | `outlook` |
1233
- | `EMAIL_USER` | Sender address | `noreply@bts-soft.com` |
1234
- | `EMAIL_PASS` | App-specific password | `abcd-efgh-ijkl-mnop` |
1235
- | `CLOUDINARY_NAME` | Media cloud name | `bts-soft-cloud` |
1236
- | `CLOUDINARY_API_KEY`| Media API key | `123456789012345` |
1237
- | `CLOUDINARY_SECRET` | Media API secret | `_shhh_secrets_` |
1238
- | `TWILIO_SID` | SMS account SID | `AC123...` |
1239
- | `TWILIO_TOKEN` | SMS auth token | `auth_tok_...` |
1240
- | `TELEGRAM_TOKEN` | Bot API token | `123456:ABC-DEF...` |
1241
-
1242
- ---
1243
-
1244
- ## Technical Appendix: Architectural Blueprint & Decision Records (ADR)
1245
-
1246
- ### ADR 001: Choosing ULID over UUID
1247
- **Decision**: Standardize on ULID for primary keys.
1248
- **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.
1249
-
1250
- ### ADR 002: Command Pattern for Uploads
1251
- **Decision**: Use the Command & Strategy pattern for the Upload module.
1252
- **Rationale**: This allows us to add new storage providers (S3, Azure Blob) without changing the `UploadService` signature, ensuring future-proof extensibility.
1253
-
1254
- ### ADR 003: BullMQ for Notifications
1255
- **Decision**: Mandatory queueing for all notification channels.
1256
- **Rationale**: Third-party APIs (Twilio, Firebase) are inherently unreliable. A persistent queue ensures that momentary network glitches do not result in dropped user notifications.
1257
-
1258
- ---
1259
-
1260
-
1261
- ## Technical Appendix: Ecosystem Roadmap 2026-2027
1262
-
1263
- 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.
1264
-
1265
- ### v2.3.0: The "Observability" Update (Q2 2026)
1266
- - **OpenTelemetry Native Support**: Built-in tracing for Redis, TypeORM, and Notification dispatch.
1267
- - **Service Mesh Helpers**: Pre-configured sidecar patterns for Istio and Linkerd.
1268
- - **Log Masking**: Automated PII detection and redaction in the `GeneralResponseInterceptor`.
1269
-
1270
- ### v2.4.0: The "Intelligence" Update (Q4 2026)
1271
- - **AI-Logic Validation**: New decorators like `@SentimentField` and `@SpamScanner` using local LLMs or external APIs.
1272
- - **Predictive Caching**: Using Redis TimeSeries to predict key expiration and warm the cache proactively.
1273
- - **Notification Sentiment Analysis**: Automated scoring of incoming (if implemented) or outgoing message tones.
1274
-
1275
- ### v3.0.0: The "Micro-Kernel" Revolution (2027)
1276
- - **Zero-Dependency Core**: Moving heavy providers (Twilio, Firebase) into optional peer-dependencies.
1277
- - **WebAssembly Transformers**: High-performance data cleaning using WASM-compiled Rust modules.
1278
- - **Native Bun/Deno Support**: Ensuring full compatibility with next-gen JavaScript runtimes.
1279
-
1280
- ---
1281
-
1282
- ## Technical Appendix: Developer Onboarding Checklist
1283
-
1284
- New to the BTS Soft ecosystem? Follow this step-by-step onboarding guide.
1285
-
1286
- ### Week 1: Foundational Setup
1287
- 1. [ ] Install the [BTS Soft VS Code Extension Pack](link).
1288
- 2. [ ] Clone the [Core Samples Repository](link).
1289
- 3. [ ] Complete the "Hello World" tutorial for the **Validation** module.
1290
- 4. [ ] Successfully send a test email via the **Notification** service.
1291
-
1292
- ### Week 2: Intermediate Patterns
1293
- 5. [ ] Implement a **Cache-Aside** logic for a database entity.
1294
- 6. [ ] Create a custom **Upload Observer** for audit logging.
1295
- 7. [ ] Build a **GraphQL Input Type** using the core decorators.
1296
-
1297
- ### Week 3: Production Readiness
1298
- 8. [ ] Perform a **SQL Injection Simulation** on your local API.
1299
- 9. [ ] Run a **Load Test** (1,000 RPS) using `k6`.
1300
- 10. [ ] Configure **Redis Persistence (AOF)** on your staging server.
1301
-
1302
- ---
1303
-
1304
- ## Technical Appendix: The Global Developer Credits
1305
-
1306
- A special thank you to all the engineers who have contributed to the `@bts-soft` codebase.
1307
-
1308
- | Contributor | Area of Expertise | Version Contribution |
1309
- | :--- | :--- | :--- |
1310
- | **Omar Sabry** | Lead Architect, Security | v1.0.0 - Present |
1311
-
1312
- ---
1313
-
1314
- ## Epilogue: The BTS Soft Legacy
1315
- 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.
1316
-
1317
- ---
1318
-
1319
- ## Credits & License
1320
- Created and maintained by **Omar Sabry** for **BTS Soft**.
1321
- Licensed under the **MIT License**.
1322
- © 2026 BTS Soft. All Rights Reserved.
1323
-
1324
- ## Enterprise Media Management Deep Dive
1325
-
1326
- 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.
1327
-
1328
- ---
1329
-
1330
- ### The Command Pattern Architecture
1331
-
1332
- 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.
1333
-
1334
- #### Available Commands
1335
- - `UploadImageCommand`: Handles image-specific validation and format conversion.
1336
- - `UploadVideoCommand`: Manages large file streams and chunked uploads.
1337
- - `UploadAudioCommand`: Processes sound files with audio metadata extraction.
1338
- - `UploadFileCommand`: Transparently handles documents and raw binary data.
1339
- - `DeleteImageCommand`: Safely removes assets and cleans up the Cloudinary cache.
1340
-
1341
- ---
1342
-
1343
- ### Strategic Media Lifecycles
1344
-
1345
- Every file upload follows a 5-step lifecycle:
1346
-
1347
- 1. **Validation Stage**: The `UploadService` checks the file extension and size against the protocol limits (e.g., 5MB for images).
1348
- 2. **Command Preparation**: A specific command is instantiated (e.g., `UploadImageCommand`) with the incoming stream and target folder.
1349
- 3. **Execution (Strategy)**: The command calls the `IUploadStrategy.upload()` method. By default, this pipes the stream directly to Cloudinary's secure servers.
1350
- 4. **Observer Notification**: On success or failure, the `LoggingObserver` (or your custom observer) is notified to log the event or trigger a DB update.
1351
- 5. **Response Sanitization**: High-level metadata (URL, public_id, size) is returned to the caller in a standardized object.
1352
-
1353
- ---
1354
-
1355
- ### Advanced Media Transformations
1356
-
1357
- Since we use Cloudinary by default, you can leverage their massive transformation CDN directly through `channelOptions`.
1358
-
1359
- ```typescript
1360
- // Example: Generate a square avatar with rounded corners
1361
- const result = await uploadService.uploadImageCore(file, 'users', {
1362
- transformation: [
1363
- { width: 500, height: 500, crop: "fill", gravity: "face" },
1364
- { radius: "max" },
1365
- { effect: "sepia" }
1366
- ]
1367
- });
1368
- ```
1369
-
1370
- ---
1371
-
1372
- ## Global Troubleshooting Matrix
1373
-
1374
- This comprehensive guide covers common errors and their solutions across all core modules.
1375
-
1376
- ### Redis Connectivity
1377
- | Error Message | Possible Root Cause | Solution |
1378
- | :--- | :--- | :--- |
1379
- | `ECONNREFUSED` | Redis server is not running or port is wrong. | Check `REDIS_HOST` and `REDIS_PORT`. |
1380
- | `NOAUTH` | Redis requires a password but none was provided. | Set `REDIS_PASSWORD` in `.env`. |
1381
- | `OOM command not allowed` | Redis has reached its memory limit. | Increase `maxmemory` in `redis.conf` or cleanup old keys. |
1382
-
1383
- ### Notification Delivery
1384
- | Error Message | Possible Root Cause | Solution |
1385
- | :--- | :--- | :--- |
1386
- | `EAUTH - Invalid credentials`| SMTP user/password is incorrect. | Check `EMAIL_USER` and `EMAIL_PASS`. |
1387
- | `Invalid phone number` | Input is not in E.164 format. | Ensure `recipientId` starts with `+` or use the auto-normalizer. |
1388
- | `403 Forbidden (Telegram)` | Bot has been blocked by the user. | User must restart the bot to receive messages. |
1389
-
1390
- ### Media Upload
1391
- | Error Message | Possible Root Cause | Solution |
1392
- | :--- | :--- | :--- |
1393
- | `Invalid image type` | Extension is not in the allowed list. | Check the 'Media Type Specifications' table above. |
1394
- | `File too large` | Stream size exceeds the module limit. | Use a separate command for large files or update limits. |
1395
- | `Must provide cloud_name` | Cloudinary config is missing. | Verify `CLOUDINARY_CLOUD_NAME` is loaded in `process.env`. |
1396
-
1397
- ---
1398
-
1399
- ## Enterprise Design Patterns & Architecture Deep Dive
1400
-
1401
- 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.
1402
-
1403
- ---
1404
-
1405
- ### 1. The Strategy Pattern (Provider Decoupling)
1406
-
1407
- In the upload and notification modules, we decouple the "What to do" from "How to do it."
1408
-
1409
- ```mermaid
1410
- classDiagram
1411
- class IUploadStrategy {
1412
- <<interface>>
1413
- +upload(stream, options)
1414
- }
1415
- class CloudinaryUploadStrategy {
1416
- -cloudinaryClient
1417
- +upload()
1418
- }
1419
- class S3UploadStrategy {
1420
- -s3Client
1421
- +upload()
1422
- }
1423
- IUploadStrategy <|.. CloudinaryUploadStrategy
1424
- IUploadStrategy <|.. S3UploadStrategy
1425
- UploadService --> IUploadStrategy
1426
- ```
1427
-
1428
- **Why this matters:**
1429
- - **Zero Lock-in**: You can switch from Cloudinary to S3 by simply creating a new strategy class and updating the factory.
1430
- - **Improved Testing**: You can inject a `MockUploadStrategy` in unit tests to avoid hitting real APIs.
1431
-
1432
- ---
1433
-
1434
- ### 2. The Command Pattern (Logic Encapsulation)
1435
-
1436
- Every specific media operation is a discrete "Command" object.
1437
-
1438
- ```mermaid
1439
- sequenceDiagram
1440
- participant App as Application Code
1441
- participant Service as UploadService
1442
- participant Command as UploadImageCommand
1443
- participant Strategy as CloudinaryStrategy
1444
-
1445
- App->>Service: uploadImageCore(stream, options)
1446
- Service->>Command: new UploadImageCommand(strategy, stream, opts)
1447
- Service->>Command: execute()
1448
- Command->>Strategy: upload(stream, opts)
1449
- Strategy-->>Command: result
1450
- Command-->>Service: result
1451
- Service-->>App: result
1452
- ```
1453
-
1454
- **Why this matters:**
1455
- - **Single Responsibility**: The `UploadService` doesn't need to know how to handle MP4 chunks or JPEG compression; it just knows how to execute commands.
1456
- - **Atomic Operations**: Each command is an isolated unit of work.
1457
-
1458
- ---
1459
-
1460
- ### 3. The Observer Pattern (Reactive Events)
1461
-
1462
- We use the Observer pattern to handle secondary effects like logging, analytics, and cache invalidation.
1463
-
1464
- ```mermaid
1465
- graph LR
1466
- Upload[Upload Command Success] --> ObserverPool[Observer Registry]
1467
- ObserverPool --> Log[LoggingObserver]
1468
- ObserverPool --> Analytics[MetricsObserver]
1469
- ObserverPool --> DB[DatabaseSyncObserver]
1470
- ```
1471
-
1472
- ---
1473
-
1474
- ## Global Configuration Schema Master List
1475
-
1476
- The following table lists every supported configuration property used across the five sub-packages.
1477
-
1478
- ### Core & Common Config
1479
- | Key | Type | Default | Description |
1480
- | :--- | :--- | :--- | :--- |
1481
- | `NODE_ENV` | `string` | `development` | Runtime environment. |
1482
- | `PORT` | `number` | `3000` | HTTP port. |
1483
-
1484
- ### Redis & Cache Config
1485
- | Key | Type | Default | Description |
1486
- | :--- | :--- | :--- | :--- |
1487
- | `REDIS_HOST` | `string` | `localhost` | Redis server address. |
1488
- | `REDIS_PORT` | `number` | `6379` | Redis server port. |
1489
- | `REDIS_PASSWORD`| `string` | `null` | Optional auth password. |
1490
-
1491
- ### Notification Hub Config
1492
- | Key | Type | Description |
1493
- | :--- | :--- | :--- |
1494
- | `EMAIL_USER` | `string` | SMTP Username. |
1495
- | `EMAIL_PASS` | `string` | SMTP App Password. |
1496
- | `TWILIO_SID` | `string` | Twilio Account SID. |
1497
- | `TELEGRAM_TOKEN`| `string` | BotFather Token. |
1498
-
1499
- ---
1500
-
1501
- ## Technical Appendix: The Giant FAQ Registry (100+ Items)
1502
-
1503
- This registry is a living document of questions, edge cases, and architectural inquiries collected from developers across the BTS Soft ecosystem.
1504
-
1505
- ---
1506
-
1507
- ### Phase 1: General Ecosystem & Architecture
1508
-
1509
- **Q1: Why was the core package split into five sub-packages?**
1510
- 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.
1511
-
1512
- **Q2: What is the primary advantage of using ULIDs over UUIDs in `BaseEntity`?**
1513
- 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.
1514
-
1515
- **Q3: Is this package compatible with Fastify?**
1516
- A: Yes. All core modules are built on top of standard NestJS abstractions, making them compatible with both Express and Fastify adapters.
1517
-
1518
- **Q4: How does the meta-package handle versioning?**
1519
- 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.
1520
-
1521
- **Q5: Can I override the global interceptors?**
1522
- 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.
1523
-
1524
- **Q6: What happens if I don't provide a Cloudinary API key?**
1525
- A: The `UploadService` will fail during initialization or throw an descriptive error when the first upload command is executed.
1526
-
1527
- **Q7: How do I contribute a new notification channel?**
1528
- A: 1. Create a new class implementing `INotificationChannel`. 2. Update the `NotificationChannelFactory`. 3. Add the necessary config to `NotificationConfigService`.
1529
-
1530
- **Q8: Does the package support multi-tenancy?**
1531
- A: The architecture supports it at the logic level (e.g., prefixing Redis keys), but there is no built-in "Tenant Selector" strategy yet.
1532
-
1533
- ---
1534
-
1535
- ### Phase 2: Validation & Enterprise Security
1536
-
1537
- **Q9: Why does `@TextField` convert everything to lowercase by default?**
1538
- A: To ensure data consistency in searches and database indexes. If you need case-sensitive fields, use the `CapitalField` or standard `class-validator` decorators.
1539
-
1540
- **Q10: Is the SQL Injection regex 100% foolproof?**
1541
- 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.
1542
-
1543
- **Q11: Can I use `@PhoneField` for American numbers?**
1544
- A: Yes, pass `'US'` as the first argument to the decorator: `@PhoneField('US')`.
1545
-
1546
- **Q12: Why is `@IsOptional()` included in most composite decorators?**
1547
- 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.
1548
-
1549
- **Q13: How do I validate a field that must be exactly 14 characters?**
1550
- A: Use `@TextField('Field', 14, 14)`.
1551
-
1552
- **Q14: Does `@EmailField` check if the domain actually exists?**
1553
- A: No, it performs structural validation (regex) only. For DNS checks, you would need a custom validator or a third-party service integration.
1554
-
1555
- **Q15: What is the performance impact of the global SQLi interceptor?**
1556
- A: Negligible. Regex checks on request bodies typically take less than 1ms, even for large payloads.
1557
-
1558
- **Q16: Can I use these decorators outside of NestJS?**
1559
- A: No, they are heavily dependent on `@nestjs/common` and the NestJS decorator metadata system.
1560
-
1561
- ---
1562
-
1563
- ### Phase 3: High-Performance Caching (Redis)
1564
-
1565
- **Q17: Why use `ioredis` instead of the native `redis` package?**
1566
- A: `ioredis` provides superior support for Promises, Clusters, Sentinels, and automatic reconnection logic.
1567
-
1568
- **Q18: How do I clear the entire cache?**
1569
- A: Use the `flushAll()` method in the `RedisService` (Warning: This is a destructive operation).
1570
-
1571
- **Q19: Can I use Redis for session management with this package?**
1572
- A: Absolutely. You can wrap the `get` and `set` methods to create a custom session store for `express-session` or `passport`.
1573
-
1574
- **Q20: What is the maximum size of a value I can store in Redis?**
1575
- A: 512MB, though we recommend keeping cached objects under 100KB for optimal network performance.
1576
-
1577
- **Q21: How do I implement a "Wait-for-Lock" logic?**
1578
- A: Use the `waitForLock(resource, timeout)` helper, which polls the `acquireLock` method until the lock is available or the timeout is reached.
1579
-
1580
- **Q22: Is the Pub/Sub system reliable?**
1581
- 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).
1582
-
1583
- **Q23: How do I handle Redis clusters?**
1584
- A: Pass the cluster node array in the `REDIS_HOST` environment variable (comma-separated). The service will automatically detect and initialize a Cluster client.
1585
-
1586
- ---
1587
-
1588
- ### Phase 4: Reliable Notifications (Reliable Hub)
1589
-
1590
- **Q24: Why use BullMQ instead of simple `Promise.all`?**
1591
- 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.
1592
-
1593
- **Q25: Can I send HTML emails with Nodemailer?**
1594
- A: Yes, pass the `html` property inside the `channelOptions` object of the `NotificationMessage`.
1595
-
1596
- **Q26: How do I set up a Telegram bot?**
1597
- A: Talk to `@BotFather` on Telegram to get a token, then set it as `TELEGRAM_BOT_TOKEN`.
1598
-
1599
- **Q27: Does the WhatsApp channel support images?**
1600
- A: Yes, pass `mediaUrl` in the `channelOptions`. Twilio will handle the delivery of the media.
1601
-
1602
- **Q28: What is the default retry delay?**
1603
- A: It uses an exponential backoff starting at 5 seconds. (5s, 10s, 20s...).
1604
-
1605
- **Q29: How do I listen for failed notification jobs?**
1606
- A: Currently, you can check the Redis `bull:notifications:failed` set or use the BullBoard UI (not included in core).
1607
-
1608
- ---
1609
-
1610
- ### Phase 5: Media & Uploads (Digital Asset Management)
1611
-
1612
- **Q30: What is the maximum file size for video uploads?**
1613
- A: By default, the `UploadVideoCommand` allows up to 100MB. This limit is set to balance server memory and Cloudinary's chunked upload efficiency.
1614
-
1615
- **Q31: How do I handle private file uploads?**
1616
- 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.
1617
-
1618
- **Q32: Does the package support local file storage?**
1619
- A: No. `@bts-soft/upload` is cloud-native and focuses on Cloudinary. To support local storage, you would need to implement a `LocalStorageStrategy`.
1620
-
1621
- **Q33: How can I prevent users from uploading large TIFF files?**
1622
- A: The `validateFile` method already limits images to `jpg`, `png`, `webp`, and `gif`. Any other extension will trigger a `400 Bad Request`.
1623
-
1624
- **Q34: What is a "Public ID" in Cloudinary?**
1625
- 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.
1626
-
1627
- **Q35: How do I generate a thumbnail for a video?**
1628
- A: Simply change the extension of the video URL to `.jpg` or use Cloudinary's dynamic transformation URL parameters.
1629
-
1630
- **Q36: Can I use this for S3?**
1631
- A: Yes, by implementing the `IUploadStrategy` for AWS SDK and injecting it into the service.
1632
-
1633
- ---
1634
-
1635
- ### Phase 6: Advanced Troubleshooting & Edge Cases
1636
-
1637
- **Q37: Why am I getting "Circular dependency" errors?**
1638
- A: This usually happens when two services in different modules import each other. Use `forwardRef(() => ...)` in your module definitions to resolve this.
1639
-
1640
- **Q38: How do I migrate from an old Redis package?**
1641
- A: Replace your direct client calls with `redisService` methods. Most common operations (get, set, del) have identical names.
1642
-
1643
- **Q39: How do I handle Arabic characters in the SQL injection check?**
1644
- A: The regex is designed to allow Arabic character ranges while still blocking SQL-specific keywords and characters like `'`, `;`, and `--`.
1645
-
1646
- **Q40: Can I use multiple Redis instances?**
1647
- A: The current `CacheModule` supports a single global instance. For multiple instances, you would need to define multiple providers using different injection tokens.
1648
-
1649
- **Q41: How do I unit test a service that uses @bts-soft/core?**
1650
- A: Mock the injected services (`RedisService`, `UploadService`, etc.) using standard NestJS testing utilities like `jest.mock()`.
1651
-
1652
- **Q42: What if my Twilio account is suspended?**
1653
- A: The notification service will throw a `401 Unauthorized` or `403 Forbidden` error. You should monitor your logs for these specific status codes.
1654
-
1655
- ---
1656
-
1657
- ### 10. `Exponential Backoff Waiter`
1658
- When waiting for a resource, don't spam the network. Scale your wait time.
1659
-
1660
- ```typescript
1661
- async waitForResource(key: string, maxRetries = 5) {
1662
- let delay = 1000;
1663
- for (let i = 0; i < maxRetries; i++) {
1664
- if (await this.redis.exists(key)) return true;
1665
- await new Promise(r => setTimeout(r, delay));
1666
- delay *= 2;
1667
- }
1668
- return false;
1669
- }
1670
- ```
1671
-
1672
- ### 11. `The Sliding Window Rate Limiter`
1673
- Using Redis Sorted Sets to implement a precise rate limiter that handles "bursty" traffic.
1674
-
1675
- ```typescript
1676
- async isAllowedInWindow(userId: string, windowInSec = 60, limit = 10) {
1677
- const key = `ratelimit:${userId}`;
1678
- const now = Date.now();
1679
- const windowStart = now - (windowInSec * 1000);
1680
-
1681
- const multi = this.redis.client.multi();
1682
- multi.zremrangebyscore(key, 0, windowStart);
1683
- multi.zadd(key, now, now.toString());
1684
- multi.zcard(key);
1685
- multi.expire(key, windowInSec);
1686
-
1687
- const results = await multi.exec();
1688
- const count = results[2][1] as number;
1689
- return count <= limit;
1690
- }
1691
- ```
1692
-
1693
- ### 12. `Partial Cache Update` (Patching)
1694
- Updating specific fields in a cached object without re-fetching everything.
1695
-
1696
- ```typescript
1697
- async patchUserCache(userId: string, partialData: Partial<User>) {
1698
- const key = `user:${userId}`;
1699
- const existing = await this.redis.get<User>(key);
1700
- if (cached) {
1701
- await this.redis.set(key, { ...existing, ...partialData });
1702
- }
1703
- }
1704
- ```
1705
-
1706
- ### 13. `The Decoupled Producer/Consumer` (Redis Lists)
1707
- A simple, low-overhead queue pattern for non-critical background tasks.
1708
-
1709
- ```typescript
1710
- // Producer
1711
- async addToGenericQueue(data: any) {
1712
- await this.redis.lPush('tasks:generic', data);
1713
- }
1714
-
1715
- // Consumer (Mock)
1716
- async processNextTask() {
1717
- const task = await this.redis.rPop('tasks:generic');
1718
- if (task) await execute(task);
1719
- }
1720
- ```
1721
-
1722
- ### 14. `HyperLogLog Unique Visitor Count`
1723
- Count millions of unique IPs using only 12KB of memory.
1724
-
1725
- ```typescript
1726
- async trackUniqueVisit(ip: string) {
1727
- await this.redis.pfAdd('stats:unique_visitors', ip);
1728
- }
1729
-
1730
- async getUniqueCount() {
1731
- return await this.redis.pfCount('stats:unique_visitors');
1732
- }
1733
- ```
1734
-
1735
- ### 15. `Geospatial User Search`
1736
- Find users within 5km of a coordinate.
1737
-
1738
- ```typescript
1739
- async findNearbyUsers(lon: number, lat: number, radius = 5) {
1740
- return await this.redis.client.georadius('users:geo', lon, lat, radius, 'km');
1741
- }
1742
- ```
1743
-
1744
- ### 16. `Bitfield Storage` (Permission Flags)
1745
- Store 64 boolean permissions in a single Redis key for extreme efficiency.
1746
-
1747
- ### 17. `Cache Warming Strategy`
1748
- Proactively fill the cache during application startup or low-traffic periods.
1749
-
1750
- ### 18. `The "Dogpile" Prevention` (Locking on Miss)
1751
- Ensure that only one request hits the database when a popular key expires.
1752
-
1753
- ### 19. `Redis Streams for Event Sourcing`
1754
- Log every state change into an append-only Redis Stream.
1755
-
1756
- ### 20. `Cross-Module Signaling` (Pub/Sub)
1757
- Trigger a clear-cache event in Module B when Module A updates a shared entity.
1758
-
1759
- ---
1760
-
1761
- ## Internal Source Code Audit: A Line-by-Line Guide
1762
-
1763
- This section is for senior architects who want to understand exactly how the sausage is made. We walk through the core logic of the absolute most critical files in the ecosystem.
1764
-
1765
- ---
1766
-
1767
- ### Audit: `sqlInjection.regex.ts` (`@bts-soft/validation`)
1768
-
1769
- The regex in this file is the heart of the security system.
1770
-
1771
- ```typescript
1772
- // Line 1-2: Defining the keyword list
1773
- const SQL_KEYWORDS = [
1774
- 'SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'UNION' ...
1775
- ];
1776
-
1777
- // Line 12: Constructing the boundary-aware capture group
1778
- // We use \b to ensure we don't block words like "Selection" or "Updateable".
1779
- const pattern = `(\\b(${SQL_KEYWORDS.join('|')})\\b)`;
1780
-
1781
- // Line 25: Adding non-word delimiters
1782
- // We catch --, /*, and # which are used for SQL comments to bypass authentication.
1783
- const fullPattern = `${pattern}|(--)|(\\/\\*)|(\\*\\/)|(')|(")|(;)|(#)`;
1784
- ```
1785
-
1786
- ### Audit: `redis.service.ts` (`@bts-soft/cache`)
1787
-
1788
- How we handle serialization and Type Safety.
1789
-
1790
- ```typescript
1791
- // Line 45: The Generic Get method
1792
- public async get<T = any>(key: string): Promise<T | null> {
1793
- const value = await this.cacheManager.get(key);
1794
- if (!value) return null;
1795
-
1796
- // Logic: We assume values in Redis are JSON strings.
1797
- // If parsing fails, we return the raw string.
1798
- try {
1799
- return JSON.parse(value as string);
1800
- } catch {
1801
- return value as unknown as T;
1802
- }
1803
- }
1804
- ```
1805
-
1806
- ### Audit: `NotificationChannel.factory.ts` (`@bts-soft/notifications`)
1807
-
1808
- The Dependency Injection orchestration.
1809
-
1810
- ```typescript
1811
- // Line 60: The Switch-based instantiation
1812
- // Note how we only instantiate a channel if it is actually called.
1813
- // This saves memory by avoiding loading heavy SDKs (like Firebase) unless needed.
1814
- public getChannel(channelType: ChannelType): INotificationChannel {
1815
- switch (channelType) {
1816
- case ChannelType.EMAIL:
1817
- return new EmailChannel(this.configService.getEmailConfig());
1818
- // ...
1819
- }
1820
- }
1821
- ```
1822
-
1823
- ---
1824
-
1825
- ### Audit: `GeneralResponseInterceptor` (`@bts-soft/common`)
1826
-
1827
- The logic for standardizing success and error envelopes.
1828
-
1829
- ```typescript
1830
- // Line 35: The Success Mapping
1831
- // We use rxjs map operator to transform the stream.
1832
- // This ensures that even if a controller returns a plain object,
1833
- // the client receives a structured response.
1834
- map((data: any) => {
1835
- return {
1836
- success: true,
1837
- statusCode: context.switchToHttp().getResponse().statusCode,
1838
- message: data?.message || 'Request successful',
1839
- data: data?.data || data,
1840
- // ...
1841
- };
1842
- })
1843
-
1844
- // Line 75: The GraphQL Error Shim
1845
- // This is critical for keeping Apollo Server responses standard.
1846
- catchError((error) => {
1847
- const response = this.formatError(error);
1848
- return throwError(() => new GraphQLError(response.message, {
1849
- extensions: response
1850
- }));
1851
- })
1852
- ```
1853
-
1854
- ### Audit: `BaseEntity.ts` (`@bts-soft/common`)
1855
-
1856
- The lifecycle hooks and ULID generation.
1857
-
1858
- ```typescript
1859
- // Line 12: The @PrimaryColumn with ULID
1860
- // We don't use @PrimaryGeneratedColumn('uuid') because we want
1861
- // lexicographical ordering.
1862
- @PrimaryColumn('varchar', { length: 26 })
1863
- id: string = ulid();
1864
-
1865
- // Line 30: The AfterInsert Hook
1866
- // Used for automatic audit logging across the entire ecosystem.
1867
- @AfterInsert()
1868
- logInsert() {
1869
- console.log(`[ENTITY_CREATED] ${this.constructor.name} ID: ${this.id}`);
1870
- }
1871
- ```
1872
-
1873
- ### Audit: `uploadImage.command.ts` (`@bts-soft/upload`)
1874
-
1875
- The bridge between the Service and the Strategy.
1876
-
1877
- ```typescript
1878
- // Line 10: The Execute Method
1879
- // The command doesn't implement upload logic; it provides the "Context".
1880
- // This follows the Command Pattern to the letter.
1881
- public async execute(): Promise<UploadResult> {
1882
- const result = await this.strategy.upload(this.stream, this.options);
1883
- this.notifyObservers('onUploadSuccess', result);
1884
- return result;
1885
- }
1886
- ```
1887
-
1888
- ---
1889
-
1890
- ## Production Hardening & High Availability
1891
-
1892
- A production deployment of `@bts-soft/core` requires specific infrastructure tuning to handle enterprise-level loads.
1893
-
1894
- ### 1. Redis High Availability (Sentinel vs. Cluster)
1895
-
1896
- Depending on your traffic, you should choose one of these two topologies:
1897
-
1898
- #### Redis Sentinel (Failover)
1899
- Recommended for most medium-scale applications.
1900
- - **Topology**: 1 Master, 2 Slavic replicas, 3 Sentinels.
1901
- - **Service Config**: Use the `sentinel` options in `ioredis`.
1902
-
1903
- #### Redis Cluster (Horizontal Scale)
1904
- Recommended for million-user applications.
1905
- - **Topology**: Multiple masters with sharding.
1906
- - **Service Config**: Enable `cluster` mode in the `CacheModule`.
1907
-
1908
- ### 2. BullMQ Worker Tuning
1909
-
1910
- The notification system can be a bottleneck if not tuned.
1911
- - **Concurrency**: Increase the number of concurrent jobs per worker (Default is 1).
1912
- - **Separation**: Run the `NotificationProcessor` in a separate microservice to avoid CPU contention with your API.
1913
-
1914
- ---
1915
-
1916
- ## Developer Productivity: IDE Snippets & Tools
1917
-
1918
- To speed up development, we recommend adding these VS Code snippets.
1919
-
1920
- ### Snippet: New DTO Field
1921
- ```json
1922
- "BTS DTO Field": {
1923
- "prefix": "bts-field",
1924
- "body": [
1925
- "@TextField('${1:Name}', ${2:3}, ${3:100})",
1926
- "${4:fieldName}: string;"
1927
- ]
1928
- }
1929
- ```
1930
-
1931
- ### Snippet: Cache-Aside Pattern
1932
- ```json
1933
- "BTS Cache-Aside": {
1934
- "prefix": "bts-cache",
1935
- "body": [
1936
- "const cacheKey = `${1:prefix}:${id}`;",
1937
- "const cached = await this.redis.get(cacheKey);",
1938
- "if (cached) return cached;",
1939
- "const data = await this.db.find(id);",
1940
- "await this.redis.set(cacheKey, data, 3600);",
1941
- "return data;"
1942
- ]
1943
- }
1944
- ```
1945
-
1946
- ---
1947
-
1948
- ## Global Error Code Reference
1949
-
1950
- This section serves as a comprehensive dictionary for every error code returnable by the external providers integrated into `@bts-soft/core`.
1951
-
1952
- ### 1. Cloudinary API Errors
1953
- | Code | Meaning | BTS Soft Recommendation |
1954
- | :--- | :--- | :--- |
1955
- | `400` | Invalid request parameters | Check your `channelOptions` and folders. |
1956
- | `401` | Authentication failed | Verify `CLOUDINARY_API_KEY` and `SECRET`. |
1957
- | `403` | Access denied / Capacity exceeded | Check your Cloudinary plan limits. |
1958
- | `404` | Asset not found | Double check the `public_id` provided. |
1959
- | `420` | Rate limit reached | Implement a retry delay or upgrade plan. |
1960
-
1961
- ### 2. Twilio (SMS/WhatsApp) Errors
1962
- | Code | Meaning | BTS Soft Recommendation |
1963
- | :--- | :--- | :--- |
1964
- | `21211` | Invalid 'To' Phone Number | Check the recipientId format and country code. |
1965
- | `21408` | Landline cannot receive SMS | Ensure the number is a mobile or SMS-enabled. |
1966
- | `21614` | To number is not a mobile number | Verify the phone type. |
1967
- | `63003` | WhatsApp message undeliverable | Check if user has opted in or if the session is open. |
1968
-
1969
- ### 3. Firebase (FCM) Errors
1970
- | Code | Meaning | BTS Soft Recommendation |
1971
- | :--- | :--- | :--- |
1972
- | `messaging/invalid-argument` | The payload is malformed. | Verify the structure of your `channelOptions`. |
1973
- | `messaging/registration-token-not-registered` | The token is no longer valid. | Remove this token from your database. |
1974
- | `messaging/message-rate-exceeded` | Too many messages sent. | Implement a queue delay. |
1975
-
1976
- ### 4. Nodemailer (SMTP) Errors
1977
- | Code | Meaning | BTS Soft Recommendation |
1978
- | :--- | :--- | :--- |
1979
- | `535` | Authentication failed | Check your app-specific password. |
1980
- | `550` | Mailbox unavailable | The recipient email does not exist. |
1981
- | `554` | Transaction failed (Spam?) | Your IP might be blacklisted. Use a relay like SendGrid. |
1982
-
1983
- ---
1984
-
1985
- ## Global Best Practices & Performance Guide
1986
-
1987
- ---
1988
-
1989
- ## Global Best Practices & Performance Guide
1990
-
1991
- **Q43: How do I handle "Connection Lost" in the Redis service?**
1992
- A: The `RedisService` utilizes `ioredis` with an automatic `retryStrategy`. By default, it will attempt to reconnect every 2 seconds, but you can configure this in the `CacheModule` options if needed.
1993
-
1994
- **Q44: Can I use multiple Cloudinary accounts?**
1995
- A: Not with the default `UploadModule`. It is configured to use a single global account. For multi-account support, you would need to instantiate multiple `Cloudinary` clients manually.
1996
-
1997
- **Q45: Does the `BaseEntity` support Soft Deletes?**
1998
- A: The current `BaseEntity` does not include `@DeleteDateColumn`. To support soft deletes, you can add this column to your specific entity class or create a `BaseSoftDeleteEntity`.
1999
-
2000
- **Q46: Is there a way to prioritize certain notification jobs?**
2001
- A: Yes, BullMQ supports job priorities. We plan to expose the `priority` option in the `NotificationMessage` interface in the next minor release (v2.3).
2002
-
2003
- **Q47: How do I change the default email template?**
2004
- A: The `EmailChannel` is designed to be template-agnostic. You should generate your HTML using a library like `handlebars` or `ejs` and pass it to the `html` field in `channelOptions`.
2005
-
2006
- **Q48: Does the WhatsApp channel support location messages?**
2007
- A: Yes, pass `persistentAction` or `location` parameters in the `channelOptions` as per Twilio’s documentation.
2008
-
2009
- **Q49: How do I handle "Rate Limit Exceeded" from Telegram?**
2010
- A: When Telegram returns a `429`, our service will bubble up the error. You should implement a "Wait-and-Retry" logic in your service or use the BullMQ backoff.
2011
-
2012
- **Q50: Can I use this package in a Serverless environment (AWS Lambda)?**
2013
- A: Yes, but you must ensure that your Redis and external API connections are handled efficiently to avoid cold-start delays. Specifically, ensure the Redis client is initialized outside the handler.
2014
-
2015
- **Q51: Why is my `GeneralResponseInterceptor` not catching errors?**
2016
- A: Ensure that you are not catching and "swallowing" errors in your service or controller before the interceptor can see them. The interceptor only catches unhandled exceptions.
2017
-
2018
- **Q52: Is there a built-in UI for managing the notification queue?**
2019
- A: No, but we recommend integrating `Bull-Board` or `Arena` into your application to monitor the Redis queue visually.
2020
-
2021
- **Q53: How do I validate a field that must be a valid ULID?**
2022
- A: Use `@IdField(26)` which enforces the exact length (26 chars) expected for a ULID.
2023
-
2024
- **Q54: Does the package support Webhooks for receiving messages?**
2025
- A: No. `@bts-soft/notifications` is exclusively for *outgoing* messages. For incoming webhooks, you must implement dedicated controllers.
2026
-
2027
- **Q55: What is the recommended Redis eviction policy?**
2028
- A: For caching, we recommend `allkeys-lru`. For BullMQ, you must ensure that your eviction policy does not delete pending jobs (i.e., set a separate Redis instance for queues).
2029
-
2030
- **Q56: How do I handle large file uploads in a low-memory environment?**
2031
- A: The `UploadService` uses streams, which means the file is never fully loaded into RAM. This makes it safe for small containers (e.g., 512MB RAM).
2032
-
2033
- **Q57: Can I use the `RedisService` for Pub/Sub and Caching simultaneously?**
2034
- A: No. Due to how Redis clients work, a client in "subscriber" mode cannot perform normal GET/SET operations. You must use two separate instances or clients.
2035
-
2036
- **Q58: Why does `@NameField` capitalize "McDonnald" incorrectly?**
2037
- A: Our `CapitalizeWords` utility uses a simple whitespace-based split. Complex names with apostrophes or interior capitals may require custom logic.
2038
-
2039
- **Q59: How do I bypass the `SqlInjectionInterceptor` for a specific route?**
2040
- A: You cannot "bypass" a global interceptor easily. If you have a route that legitimately needs to receive SQL-like strings (e.g., a code editor), you should apply the interceptor at the controller level instead of globally.
2041
-
2042
- ---
2043
-
2044
- ### Phase 7: Deep Architecture & Performance FAQ
2045
-
2046
- **Q60: What is the bottleneck in the Notification system?**
2047
- A: Usually the network latency to the third-party providers (Twilio, Firebase). This is why the asynchronous queue architecture is mandatory.
2048
-
2049
- **Q61: How many IOps can the RedisService handle?**
2050
- A: On a standard Redis instance, the service can handle upwards of 10,000 GET/SET operations per second with less than 2ms latency.
2051
-
2052
- **Q62: Should I use `@Exclude()` on the `id` field of a `BaseEntity`?**
2053
- A: Only if you want to hide the ID from the API response (security by obscurity). Generally, IDs are harmless and necessary for client-side state management.
2054
-
2055
- **Q63: Does the `UploadService` support parallel uploads?**
2056
- A: Yes. Since it is stateless and uses streams, you can execute multiple upload commands concurrently without any cross-talk.
2057
-
2058
- **Q64: How do I debug hidden `class-transformer` issues?**
2059
- A: Set the logger level to `debug` in your `main.ts` and monitor the `Transform` operations in the console.
2060
-
2061
- **Q65: Can I use this with NestJS Microservices (NATS/Inspur/RabbitMQ)?**
2062
- A: Yes. All service logic is decoupled from HTTP and will work perfectly inside a Microservice controller.
2063
- ## The SQL Injection Defense Wiki
2064
-
2065
- This section provides a deep technical analysis of how `@bts-soft/core` protects your data from malicious SQL injection attacks.
2066
-
2067
- ---
2068
-
2069
- ### Understanding the Threat Landscape
2070
-
2071
- SQL Injection (SQLi) remains one of the most critical vulnerabilities in modern web applications. At its core, SQLi occurs when untrusted data is concatenated directly into a database query.
2072
-
2073
- ### Tier 1: The `SQL_INJECTION_REGEX`
2074
-
2075
- Our primary defense is a highly optimized regular expression designed to catch the most dangerous SQL keywords and syntax patterns.
2076
-
2077
- **The Regex Pattern:**
2078
- ```regex
2079
- /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|TRUNCATE|ALTER|EXEC|CREATE|GRANT|REVOKE|DATABASE|SCHEMA|TABLE|COLUMN|FROM|WHERE|HAVING|ORDER\s+BY|GROUP\s+BY|LIMIT|OFFSET|JOIN|INNER|OUTER|LEFT|RIGHT|FULL|CROSS|NATURAL|INTO|VALUES|SET|TABLESPACE|TRIGGER|PROCEDURE|FUNCTION|VIEW|INDEX|ALL|ANY|SOME|EXISTS|BETWEEN|LIKE|SIMILAR|ILIKE|REGEX|REGEXP|GLOB|SOUNDS|MATCH|ESCAPE|OR|AND|XOR|NOT|AS|IS|NULL|TRUE|FALSE|UNKNOWN|COALESCE|NULLIF|IFNULL|ISNULL|IF|THEN|ELSE|CASE|WHEN|END|WAITFOR|DELAY|SLEEP|BENCHMARK|HEX|MD5|SHA1|SHA2|CHRT|ORD|ASCII|CONCAT|SUBSTR|SUBSTRING|LENGTH|CHAR_LENGTH|BIT_LENGTH|OCTET_LENGTH|UPPER|LOWER|INITCAP|TRIM|LTRIM|RTRIM|RPAD|LPAD|REPLACE|TRANSLATE|REPEAT|REVERSE|QUOTE|UNQUOTE|DUMP|VSIZE|UID|USER|SESSION_USER|SYSTEM_USER|CURRENT_USER|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|LOCALTIME|LOCALTIMESTAMP|CAST|CONVERT|TO_DATE|TO_TIMESTAMP|TO_NUMBER|TO_CHAR|TRUNC|ROUND|MOD|ABS|CEIL|FLOOR|EXP|LN|LOG|POWER|SQRT|PI|SIN|COS|TAN|ASIN|ACOS|ATAN|EXP|LN|LOG|POWER|SQRT|PI|SIN|COS|TAN|ASIN|ACOS|ATAN)\b)|(--)|(\/\*)|(\*\/)|(\')|(\")|(\;)|(\#)/gi
2080
- ```
2081
-
2082
- **What it blocks:**
2083
- 1. **DML Keywords**: `SELECT`, `INSERT`, `UPDATE`, `DELETE`.
2084
- 2. **DDL Keywords**: `DROP`, `ALTER`, `CREATE`, `TRUNCATE`.
2085
- 3. **Comments**: `--`, `/*`, `#`.
2086
- 4. **Terminators**: `;`.
2087
- 5. **Sensitive Functions**: `SLEEP()`, `BENCHMARK()`, `MD5()`.
2088
- 6. **Boolean Logic**: `OR 1=1`, `AND 1=0`.
2089
-
2090
- ---
2091
-
2092
- ### Tier 2: The Global Interceptor (`SqlInjectionInterceptor`)
2093
-
2094
- The `SqlInjectionInterceptor` ensures that this regex is applied to *every* incoming request.
2095
-
2096
- #### How it works:
2097
- 1. **Iteration**: It recursively traverses the `Body`, `Query`, and `Params` objects.
2098
- 2. **Scrubbing**: Every string value is tested against the regex.
2099
- 3. **Bail-out**: If a match is found, it immediately throws a `400 Bad Request` with the message: `"SQL Injection detected in field: [fieldName]"`.
2100
-
2101
- ---
2102
-
2103
- ### Tier 3: Validation Decorators
2104
-
2105
- For granular control, decorators like `@TextField` and `@EmailField` apply the same regex at the DTO level. This provides better error messages and integrates with `ValidationPipe`.
2106
-
2107
- ---
2108
-
2109
- ## 20+ Advanced Redis Design Patterns
2110
-
2111
- Building on the basic patterns mentioned earlier, here are advanced strategies for high-scale applications.
2112
-
2113
- ### 1. The "Read-Through" Service Cache
2114
- Ensures your database is never overwhelmed by high-traffic spikes.
2115
-
2116
- ```typescript
2117
- async getProductSmartly(id: string) {
2118
- return await this.redis.getOrSet(`prod:${id}`, async () => {
2119
- return await this.db.products.findOne(id);
2120
- }, 3600);
2121
- }
2122
- ```
2123
-
2124
- ### 2. "Atomic ID Generator" (Distributed)
2125
- Generate unique, gaps-free numeric IDs across multiple app instances.
2126
-
2127
- ```typescript
2128
- async getNextInvoiceNumber() {
2129
- return await this.redis.incr('infra:invoice_counter');
2130
- }
2131
- ```
2132
-
2133
- ### 3. "Real-time Leaderboard" with Expiring Scores
2134
- Perfect for "Trending Now" features.
2135
-
2136
- ```typescript
2137
- async trackView(productId: string) {
2138
- const score = Date.now();
2139
- await this.redis.zAdd('trending_products', score, productId);
2140
- // Optional: Automatically expire old scores using a scheduled task or Lua
2141
- }
2142
- ```
2143
-
2144
- ### 4. "Distributed Locking" for Finalizing Payments
2145
- Prevent double-charging users by locking the payment process.
2146
-
2147
- ```typescript
2148
- async processPayment(userId: string) {
2149
- const lock = await this.redis.acquireLock(`pay:${userId}`, 'locking_val', 30);
2150
- if (!lock) throw new ConflictException('Payment in progress');
2151
-
2152
- try {
2153
- // payment logic
2154
- } finally {
2155
- await this.redis.releaseLock(`pay:${userId}`, 'locking_val');
2156
- }
2157
- }
2158
- ```
2159
-
2160
- ### 5. "User Session Tracker" (Active Users)
2161
- Store the set of active users with a sliding window.
2162
-
2163
- ```typescript
2164
- async markUserActive(userId: string) {
2165
- await this.redis.sAdd('active_users', userId);
2166
- await this.redis.expire('active_users', 900); // 15 min window
2167
- }
2168
- ```
1139
+ import { GraphqlModule } from '@bts-soft/core';
2169
1140
 
2170
- ### 6. "Heavy Hitter" Tracking (Bloom Filters)
2171
- Approximate counting for massive datasets where space is at a premium.
2172
-
2173
- ### 7. "Job Rate Limiting"
2174
- Ensure your worker doesn't overwhelm an external API.
2175
-
2176
- ### 8. "Cache Versioning"
2177
- Force invalidation of all keys by appending a global version prefix.
2178
-
2179
- ### 9. "Tiered Expiration" (Hot/Cold Data)
2180
- Keep hot data for 1 hour and warm data for 24 hours.
2181
-
2182
- ---
2183
-
2184
- ## Global Best Practices & Performance Guide
2185
-
2186
- To get the most out of `@bts-soft/core`, follow these enterprise-grade best practices.
1141
+ @Module({
1142
+ imports: [
1143
+ GraphqlModule.forRoot({
1144
+ autoSchemaFile: true,
1145
+ playground: true,
1146
+ federation: true,
1147
+ webSocket: { enabled: true, path: '/graphql', keepAlive: 10_000 },
1148
+ }),
1149
+ ],
1150
+ })
1151
+ export class AppModule {}
1152
+ ```
2187
1153
 
2188
- ### 1. Caching Strategy: The "Golden Rule"
2189
- Never store huge objects in Redis. If you have a list of 5000 items, store the *IDs* in Redis and fetch the items from the DB, or store the list in a compressed format. Redis is fastest when keys are small (<5KB).
1154
+ #### TranslationModule
2190
1155
 
2191
- ### 2. Notification Resilience
2192
- Always use the BullMQ queue for production workloads. Direct service calls (`channel.send()`) should be reserved for testing or internal tools where latency and failure don't impact the end-user.
1156
+ Integrates `nestjs-i18n` with automatic locale resolution from `x-lang` and `Accept-Language` headers:
2193
1157
 
2194
- ### 3. Media Optimization
2195
- Leverage Cloudinary's `f_auto` and `q_auto` parameters. Our `UploadImageCommand` applies these by default. This ensures that a user on a mobile device receives a WebP image, while a user on Safari receives a JPEG.
1158
+ ```typescript
1159
+ import { TranslationModule } from '@bts-soft/core';
2196
1160
 
2197
- ---
1161
+ @Module({ imports: [TranslationModule] })
1162
+ export class AppModule {}
1163
+ ```
2198
1164
 
2199
- ## Enterprise Migration & Modernization Roadmap
1165
+ ### Resilience: Idempotency and Distributed Locking
2200
1166
 
2201
- ### Moving from Legacy to `@bts-soft/core`
1167
+ ```typescript
1168
+ import {
1169
+ Idempotent, IdempotencyInterceptor,
1170
+ DistributedLock,
1171
+ } from '@bts-soft/core';
2202
1172
 
2203
- 1. **Phase 1: Dependency Cleanup**: Remove old packages like `cache-manager-redis-store` and `cloudinary-nestjs`.
2204
- 2. **Phase 2: Database Alignment**: Update your entities to extend `BaseEntity`. Run a migration to convert `INT` IDs to `VARCHAR(26)` if moving to ULIDs.
2205
- 3. **Phase 3: DTO Modernization**: Replace your basic `IsString()` decorators with our composite ones (e.g., `@TextField()`).
2206
- 4. **Phase 4: Service Refactoring**: Inject `RedisService` and `UploadService` into your business logic layers.
1173
+ @Controller('payments')
1174
+ export class PaymentController {
1175
+ @Post('charge')
1176
+ @UseInterceptors(IdempotencyInterceptor)
1177
+ @Idempotent({ ttl: 60, headerName: 'x-idempotency-key' })
1178
+ async chargeCard(@Body() body: ChargeDto) {
1179
+ return { transactionId: 'tx_123' };
1180
+ }
2207
1181
 
2208
- ---
1182
+ @Post('transfer')
1183
+ @DistributedLock((body) => `lock:user:${body.userId}`, { ttlMs: 5_000 })
1184
+ async transferFunds(@Body() body: TransferDto) {
1185
+ return { status: 'transferred' };
1186
+ }
1187
+ }
1188
+ ```
2209
1189
 
2210
- ## Technical Glossary of Terms
1190
+ ### Sub-Path Export Matrix
2211
1191
 
2212
- | Term | Definition |
1192
+ | Sub-path | Exported Components |
2213
1193
  | :--- | :--- |
2214
- | **ULID** | Universally Unique Lexicographically Sortable Identifier. |
2215
- | **BOM** | Bill of Materials (A meta-package that versions children). |
2216
- | **Strategy** | A design pattern for swappable algorithms or providers. |
2217
- | **Command** | An object encapsulating all information needed to perform an action. |
2218
- | **FCM** | Firebase Cloud Messaging. |
2219
- | **SQLi** | SQL Injection (A malicious attack pattern). |
1194
+ | `@bts-soft/common` | Core bases, decorators, DTOs, filters, interceptors, `IdGenerator`, infrastructure modules |
1195
+ | `@bts-soft/common/typeorm` | `TypeOrmBaseEntity` |
1196
+ | `@bts-soft/common/sequelize` | `SequelizeBaseEntity` |
1197
+ | `@bts-soft/common/mongoose` | `MongooseBaseEntity` |
1198
+ | `@bts-soft/common/prisma` | `PrismaBase`, `IPrismaEntity` |
1199
+ | `@bts-soft/common/resilience` | `ResilienceModule`, `IdempotencyInterceptor`, `@Idempotent()`, `@DistributedLock()`, `DistributedLockService` |
2220
1200
 
2221
- ---
2222
-
2223
- ## Full Example Codebase: The Marketplace Service
2224
-
2225
- This section provides a complete, production-grade implementation of a "Marketplace Service" that integrates every single sub-package within the `@bts-soft/core` ecosystem.
2226
-
2227
- ### 1. The Entity Layer (`@bts-soft/common`)
1201
+ ### Production Utilities
2228
1202
 
2229
1203
  ```typescript
2230
- import { Entity, Column } from 'typeorm';
2231
- import { BaseEntity } from '@bts-soft/core';
1204
+ import { disableConsoleInProduction, displayAppBanner } from '@bts-soft/core';
2232
1205
 
2233
- @Entity('products')
2234
- export class Product extends BaseEntity {
2235
- @Column()
2236
- name: string;
2237
-
2238
- @Column('decimal')
2239
- price: number;
2240
-
2241
- @Column({ nullable: true })
2242
- imageUrl: string;
1206
+ async function bootstrap() {
1207
+ const app = await NestFactory.create(AppModule);
1208
+ setupInterceptors(app);
1209
+ disableConsoleInProduction(); // No-ops all console.* in NODE_ENV=production
1210
+ await app.listen(3000);
1211
+ displayAppBanner('My Service', 3000);
2243
1212
  }
2244
1213
  ```
2245
1214
 
2246
- ### 2. The Data Transfer Layer (`@bts-soft/validation`)
2247
-
2248
- ```typescript
2249
- import { TextField, NumberField, UrlField } from '@bts-soft/core';
2250
-
2251
- export class CreateProductDto {
2252
- @TextField('Product Name', 3, 100)
2253
- name: string;
1215
+ ---
2254
1216
 
2255
- @NumberField(false, 0.01, 100000)
2256
- price: number;
1217
+ ## Cross-Package Integration Scenarios
2257
1218
 
2258
- @UrlField(true)
2259
- imageUrl?: string;
2260
- }
2261
- ```
1219
+ ### Scenario A: Secure User Registration
2262
1220
 
2263
- ### 3. The Business Logic Layer (`@bts-soft/cache`, `@bts-soft/notifications`, `@bts-soft/upload`)
1221
+ Demonstrates: Validation, Cache, Common, Notifications
2264
1222
 
2265
1223
  ```typescript
2266
- import { Injectable } from '@nestjs/common';
2267
- import { RedisService, NotificationService, UploadService, ChannelType } from '@bts-soft/core';
2268
-
2269
1224
  @Injectable()
2270
- export class ProductService {
1225
+ export class AuthService {
2271
1226
  constructor(
2272
- private redis: RedisService,
2273
- private notif: NotificationService,
2274
- private upload: UploadService,
2275
- private productRepo: ProductRepository,
1227
+ private readonly redis: RedisService,
1228
+ private readonly notifications: NotificationService,
1229
+ private readonly userRepo: UserRepository,
2276
1230
  ) {}
2277
1231
 
2278
- async create(dto: CreateProductDto, image?: any) {
2279
- // 1. Handle Upload
2280
- let imageUrl = dto.imageUrl;
2281
- if (image) {
2282
- const uploadResult = await this.upload.uploadImageCore(image, 'products');
2283
- imageUrl = uploadResult.url;
2284
- }
2285
-
2286
- // 2. Persist to DB
2287
- const product = this.productRepo.create({ ...dto, imageUrl });
2288
- await this.productRepo.save(product);
2289
-
2290
- // 3. Cache the new product
2291
- await this.redis.set(`product:${product.id}`, product, 3600);
2292
-
2293
- // 4. Notify Admin
2294
- await this.notif.send(ChannelType.TELEGRAM, {
2295
- recipientId: process.env.ADMIN_CHAT_ID,
2296
- body: `📦 New Product Created: ${product.name} ($${product.price})`
2297
- });
2298
-
2299
- return product;
2300
- }
2301
-
2302
- async findOne(id: string) {
2303
- const cacheKey = `product:${id}`;
2304
-
2305
- // Attempt cache hit
2306
- const cached = await this.redis.get(cacheKey);
2307
- if (cached) return cached;
2308
-
2309
- // Fetch on miss
2310
- const product = await this.productRepo.findOneBy({ id });
2311
- if (product) {
2312
- await this.redis.set(cacheKey, product, 3600);
1232
+ async register(dto: RegisterUserDto): Promise<void> {
1233
+ // Validation runs automatically via the ValidationPipe before this method executes.
1234
+ // @NameField: "omar sabry" -> "Omar Sabry"
1235
+ // @EmailField: "USER@MAIL.COM" -> "user@mail.com"
1236
+ // @PasswordField: enforces COMPREHENSIVE complexity
1237
+
1238
+ // Prevent duplicate registration under race conditions
1239
+ const lockKey = `registration:lock:${dto.email}`;
1240
+ const lockToken = randomUUID();
1241
+ const acquired = await this.redis.acquireLock(lockKey, lockToken, 30_000);
1242
+ if (!acquired) throw new ConflictException('Registration already in progress');
1243
+
1244
+ try {
1245
+ // Persist with auto-generated ULID from BaseEntity
1246
+ const user = await this.userRepo.create(dto);
1247
+
1248
+ // Cache user profile for fast subsequent reads
1249
+ await this.redis.set(`user:profile:${user.id}`, user, 3600);
1250
+
1251
+ // Queue welcome email asynchronously - does not block the API response
1252
+ await this.notifications.send(ChannelType.EMAIL, {
1253
+ recipientId: user.email,
1254
+ subject: 'Welcome to BTS Soft',
1255
+ body: 'Hi {{name}}, your account is ready.',
1256
+ context: { name: user.name },
1257
+ });
1258
+ } finally {
1259
+ await this.redis.releaseLock(lockKey, lockToken);
2313
1260
  }
2314
-
2315
- return product;
2316
1261
  }
2317
1262
  }
2318
1263
  ```
2319
1264
 
2320
- ---
2321
-
2322
- ## Comprehensive Interface Registry
2323
-
2324
- This registry documents every property of the main interfaces used across the core sub-packages.
2325
-
2326
- ### 1. `NotificationMessage`
2327
- | Property | Type | Required | Description |
2328
- | :--- | :--- | :--- | :--- |
2329
- | `recipientId` | `string` | Yes | Target identifier (Email, Phone, ChatID, Token). |
2330
- | `body` | `string` | Yes | The text content of the message. |
2331
- | `title` | `string` | No | Subject line or Push title. |
2332
- | `subject` | `string` | No | Explicit field for Email subjects. |
2333
- | `channelOptions`| `any` | No | Provider-specific properties (HTML, Embeds, etc.). |
2334
-
2335
- ### 2. `UploadResult`
2336
- | Property | Type | Description |
2337
- | :--- | :--- | :--- |
2338
- | `url` | `string` | The secure, absolute URL of the hosted asset. |
2339
- | `size` | `number` | File size in bytes. |
2340
- | `filename` | `string` | Original filename as provided. |
2341
- | `type` | `string` | Media type (image, video, etc.). |
2342
- | `public_id` | `string` | Cloudinary unique identifier. |
2343
-
2344
- ### 3. `IRedisInterface` (Extended)
2345
- | Property | Type | Description |
2346
- | :--- | :--- | :--- |
2347
- | `client` | `any` | Access to the raw ioredis client for edge cases. |
2348
- | `prefix` | `string` | The global key prefix used in the current instance. |
2349
-
2350
- ---
2351
-
2352
- ## Advanced Architecture: Observer Implementation Guide
1265
+ ### Scenario B: Cache-Aside Pattern for High-Traffic Reads
2353
1266
 
2354
- To create a custom observer for the upload module, implement the `IUploadObserver` interface:
1267
+ Demonstrates: Cache, Common
2355
1268
 
2356
1269
  ```typescript
2357
- import { IUploadObserver } from '@bts-soft/core';
1270
+ @Injectable()
1271
+ export class ProductService {
1272
+ constructor(
1273
+ private readonly redis: RedisService,
1274
+ private readonly productRepo: ProductRepository,
1275
+ ) {}
2358
1276
 
2359
- export class DatabaseSyncObserver implements IUploadObserver {
2360
- onUploadSuccess(result: any) {
2361
- // Sync logic
1277
+ async getProduct(id: string): Promise<Product> {
1278
+ // Returns cached value if present, otherwise fetches, caches, and returns
1279
+ return this.redis.getOrSet(
1280
+ `product:${id}`,
1281
+ () => this.productRepo.findOneOrFail({ where: { id } }),
1282
+ 600, // Cache for 10 minutes
1283
+ );
2362
1284
  }
2363
- onUploadError(err: Error) {
2364
- // Error handling logic
2365
- }
2366
- onDeleteSuccess(res: any) {}
2367
- onDeleteError(err: Error) {}
2368
1285
  }
2369
1286
  ```
2370
1287
 
2371
- ---
2372
-
2373
- ## Technical Appendix: Final Developer Notes
2374
-
2375
- ### Performance Checklist
2376
- - [ ] Use `mSet` and `mGet` when handling groups of keys.
2377
- - [ ] Ensure `CLOUDINARY_AUTO` is enabled for images.
2378
- - [ ] Use `BullMQ` for all user-facing notification flows.
2379
- - [ ] Regularly monitor Redis memory usage using `info memory`.
2380
-
2381
- ### Security Hardening
2382
- - [ ] Rotate `TWILIO_AUTH_TOKEN` every 6 months.
2383
- - [ ] Use `app-specific passwords` for SMTP connections.
2384
- - [ ] Ensure `SQL_INJECTION_REGEX` matches your domain-specific forbidden characters.
2385
-
2386
- ---
2387
-
2388
- ## Notification Channel Deep Configuration Reference
2389
-
2390
- This section provides an exhaustive property-by-property reference for the `channelOptions` of every supported notification provider.
2391
-
2392
- ---
2393
-
2394
- ### 1. Email (Nodemailer Options)
2395
- When using the Email channel, `channelOptions` is passed directly to `transporter.sendMail()`.
2396
-
2397
- | Property | Type | Description |
2398
- | :--- | :--- | :--- |
2399
- | `from` | `string` | Override the default sender email. |
2400
- | `replyTo` | `string` | Address for user replies. |
2401
- | `cc` | `string \| string[]` | Carbon copy recipients. |
2402
- | `bcc` | `string \| string[]` | Blind carbon copy recipients. |
2403
- | `headers` | `object` | Custom SMTP headers. |
2404
- | `attachments` | `Attachment[]` | Array of Nodemailer attachment objects. |
2405
- | `html` | `string` | HTML content of the email. |
2406
- | `priority` | `'high' \| 'low' \| 'normal'` | Email priority header. |
2407
-
2408
- ---
2409
-
2410
- ### 2. WhatsApp & SMS (Twilio Options)
2411
- These options are passed to the Twilio Message creation API.
2412
-
2413
- | Property | Type | Description |
2414
- | :--- | :--- | :--- |
2415
- | `mediaUrl` | `string[]` | URLs of media files to send (WhatsApp only). |
2416
- | `statusCallback` | `string` | URL for Twilio to send delivery status events. |
2417
- | `validityPeriod` | `number` | How long the message should stay in the queue (seconds). |
2418
- | `smartEncoded` | `boolean` | Automatic Unicode detection and conversion. |
2419
- | `provideFeedback` | `boolean` | Whether Twilio should track user interaction. |
2420
-
2421
- ---
2422
-
2423
- ### 3. Firebase Cloud Messaging (FCM Options)
2424
- These options follow the `admin.messaging.MessagingOptions` and `admin.messaging.Message` structures.
2425
-
2426
- | Property | Type | Description |
2427
- | :--- | :--- | :--- |
2428
- | `data` | `Record<string, string>` | Custom key-value pairs for the app. |
2429
- | `notification` | `Notification` | Visual notification properties (Icon, Sound, Badge). |
2430
- | `android` | `AndroidConfig` | Android-specific delivery options (ChannelId, Priority). |
2431
- | `apns` | `ApnsConfig` | iOS-specific delivery options (Payload, Headers). |
2432
- | `webpush` | `WebpushConfig` | Web-specific delivery options (FCM options, Headers). |
2433
- | `condition` | `string` | Topic condition (e.g., `'topicA' in topics && 'topicB' in topics`). |
2434
-
2435
- ---
2436
-
2437
- ### 4. Telegram Bot API Options
2438
- Passed directly to `bot.sendMessage()`.
2439
-
2440
- | Property | Type | Description |
2441
- | :--- | :--- | :--- |
2442
- | `parse_mode` | `'Markdown' \| 'HTML' \| 'MarkdownV2'` | How to parse the message body. |
2443
- | `disable_web_page_preview` | `boolean` | Hide link previews in the chat. |
2444
- | `disable_notification` | `boolean` | Send the message silently. |
2445
- | `reply_to_message_id` | `number` | Target of a reply message. |
2446
- | `reply_markup` | `object` | Inline keyboards or custom reply buttons. |
2447
-
2448
- ---
2449
-
2450
- ## Redis Service Logic Internals: A Comprehensive Walkthrough
1288
+ ### Scenario C: Real-Time Event Broadcasting
2451
1289
 
2452
- The `RedisService` is a sophisticated wrapper that handles complex serialization logic. Let's walk through the `set` method line-by-line.
1290
+ Demonstrates: Cache Pub/Sub, Notifications
2453
1291
 
2454
1292
  ```typescript
2455
- // Line 25: The Entry Point
2456
- public async set(key: string, value: any, ttl: number = 3600): Promise<void> {
2457
- // Line 27: Performance Logging
2458
- // We log the operation start time to our internal Prometheus metrics.
2459
- const start = Date.now();
2460
-
2461
- // Line 30: Serialization Logic
2462
- // We check if the value is an object. If so, we JSON.stringify it.
2463
- // This ensures that complex DTOs are stored as retrievable strings.
2464
- let stringValue: string;
2465
- if (typeof value === 'object') {
2466
- stringValue = JSON.stringify(value);
2467
- } else {
2468
- stringValue = String(value);
2469
- }
1293
+ @Injectable()
1294
+ export class OrderEventBus implements OnModuleInit {
1295
+ constructor(
1296
+ private readonly redis: RedisService,
1297
+ private readonly notifications: NotificationService,
1298
+ ) {}
2470
1299
 
2471
- // Line 40: Cache-Manager Handshake
2472
- // We delegate the storage to the underlying cache-manager bridge.
2473
- // This allows us to use Redis today and potentially Memcached tomorrow.
2474
- await this.cacheManager.set(key, stringValue, { ttl });
1300
+ async onModuleInit() {
1301
+ await this.redis.subscribe('events:order_shipped', async (data) => {
1302
+ await this.notifications.send(ChannelType.FIREBASE_FCM, {
1303
+ recipientId: data.userFcmToken,
1304
+ title: 'Order Shipped',
1305
+ body: `Your order #${data.orderId} is on the way.`,
1306
+ });
1307
+ });
1308
+ }
2475
1309
 
2476
- // Line 45: Observability
2477
- // We track the latency of the operation.
2478
- this.logger.debug(`[REDIS_SET] ${key} completed in ${Date.now() - start}ms`);
1310
+ async publishOrderShipped(orderId: string, userFcmToken: string) {
1311
+ await this.redis.publish('events:order_shipped', { orderId, userFcmToken });
1312
+ }
2479
1313
  }
2480
1314
  ```
2481
1315
 
2482
- ---
2483
-
2484
- ## Technical Appendix: Performance Tuning for Massive Scale
2485
-
2486
- When your application reaches 100,000 requests per minute, these optimizations become mandatory.
2487
-
2488
- ### 1. Redis Pipelining
2489
- Instead of sending 100 `SET` commands one by one, use `mSet` or the raw `client.pipeline()` method. This reduces network round-trips by 99%.
2490
-
2491
- ### 2. Message Batching
2492
- When sending marketing notifications, batch your jobs into groups of 500. This prevents the BullMQ Redis instance from becoming a CPU bottleneck.
2493
-
2494
- ### 3. Connection Pooling
2495
- Ensure that `maxConnections` in your `ioredis` config is set higher than the number of worker threads.
2496
-
2497
- ---
2498
-
2499
- ## Technical Appendix: Security Hardening Master Checklist
2500
-
2501
- Follow this checklist before moving any project using `@bts-soft/core` to production.
2502
-
2503
- - [ ] **Redis Isolation**: Run Redis on a private VPC subnet. Disable public access.
2504
- - [ ] **Password Strength**: Ensure `REDIS_PASSWORD` is at least 32 characters long.
2505
- - [ ] **TLS Everywhere**: Force TLS for SMTP, Cloudinary, and Twilio connections.
2506
- - [ ] **Sanitization Audit**: Verify that all incoming user strings are decorated with `@TextField` or similar.
2507
- - [ ] **Rate Limiting**: Enable the `ThrottlerModule` with a maximum of 100 requests per minute per IP.
2508
- - [ ] **Observer Security**: Ensure your `LoggingObserver` does not log sensitive data (Passwords, Tokens).
2509
- - [ ] **Dependency Hygiene**: Run `npm audit` weekly to catch vulnerabilities in upstream packages.
2510
-
2511
- ---
2512
-
2513
- ## Technical Appendix: SQL Injection Pattern Database (Advanced)
2514
-
2515
- Below is a list of the most sophisticated SQL injection patterns and how our regex neutralized them.
2516
-
2517
- ### 1. The "Tautology" Attack
2518
- **Payload**: `' OR 1=1 --`
2519
- - **Detection**: The regex catches the `'`, the `OR`, and the `--` (comment initiator).
2520
- - **Result**: `400 Bad Request`.
2521
-
2522
- ### 2. The "Union-Based" Extraction
2523
- **Payload**: `UNION SELECT username, password FROM users`
2524
- - **Detection**: The regex identifies `UNION`, `SELECT`, and `FROM` as high-risk keywords.
2525
- - **Result**: `400 Bad Request`.
2526
-
2527
- ### 3. The "Time-Based Blind" Injection
2528
- **Payload**: `; IF (1=1) WAITFOR DELAY '0:0:5' --`
2529
- - **Detection**: The regex catches the `;`, `IF`, `WAITFOR`, `DELAY`, and `--`.
2530
- - **Result**: `400 Bad Request`.
2531
-
2532
- ### 4. The "Second-Order" Injection
2533
- **Payload**: `admin'--` stored in a username field.
2534
- - **Detection**: Even if it bypasses a weak front-end check, our `@TextField` decorator catches the `'` and `--` during DTO validation.
2535
-
2536
- ---
2537
-
2538
- ## Technical Appendix: Infrastructure Health Check Scripts
2539
-
2540
- You can use these `curl` commands to verify your `@bts-soft/core` health.
2541
-
2542
- ### 1. Check Redis Connection
2543
- ```bash
2544
- curl -X GET http://localhost:3000/health/redis
2545
- ```
2546
-
2547
- ### 2. Check Notification Queue
2548
- ```bash
2549
- curl -X GET http://localhost:3000/health/notifications
2550
- ```
1316
+ ### Scenario D: Preference-Aware Multi-Channel Broadcaster
2551
1317
 
2552
- ### 3. Test SQL Injection Protection
2553
- ```bash
2554
- curl -X POST http://localhost:3000/auth/login \
2555
- -H "Content-Type: application/json" \
2556
- -d '{"username": "admin\" OR 1=1", "password": "any"}'
2557
- ```
1318
+ Demonstrates: Notifications, Cache
2558
1319
 
2559
- ---
1320
+ ```typescript
1321
+ @Injectable()
1322
+ export class NotificationOrchestrator {
1323
+ constructor(
1324
+ private readonly notifications: NotificationService,
1325
+ private readonly redis: RedisService,
1326
+ ) {}
2560
1327
 
2561
- ## Technical Appendix: Custom Observer Workshop
1328
+ async notifyUser(user: User, payload: NotificationPayload) {
1329
+ const prefKey = `user:notification-prefs:${user.id}`;
1330
+ const prefs = await this.redis.get<UserNotificationPrefs>(prefKey);
2562
1331
 
2563
- Learn how to build a Slack Notification Observer for your upload module.
1332
+ const jobs = [];
2564
1333
 
2565
- ```typescript
2566
- import { IUploadObserver } from '@bts-soft/core';
2567
- import { HttpService } from '@nestjs/axios';
1334
+ if (prefs?.email !== false && user.email) {
1335
+ jobs.push(this.notifications.send(ChannelType.EMAIL, {
1336
+ recipientId: user.email,
1337
+ ...payload,
1338
+ }));
1339
+ }
2568
1340
 
2569
- export class SlackUploadObserver implements IUploadObserver {
2570
- constructor(private http: HttpService, private webhookUrl: string) {}
1341
+ if (prefs?.sms !== false && user.phone) {
1342
+ jobs.push(this.notifications.send(ChannelType.SMS, {
1343
+ recipientId: user.phone,
1344
+ body: payload.body,
1345
+ }));
1346
+ }
2571
1347
 
2572
- async onUploadSuccess(result: any) {
2573
- await this.http.post(this.webhookUrl, {
2574
- text: `🚀 File uploaded successfully: ${result.url}`
2575
- }).toPromise();
2576
- }
1348
+ if (prefs?.push !== false && user.fcmToken) {
1349
+ jobs.push(this.notifications.send(ChannelType.FIREBASE_FCM, {
1350
+ recipientId: user.fcmToken,
1351
+ title: payload.subject,
1352
+ body: payload.body,
1353
+ }));
1354
+ }
2577
1355
 
2578
- onUploadError(err: Error) {
2579
- // Log to Slack as an error alert
1356
+ await Promise.all(jobs);
2580
1357
  }
2581
-
2582
- onDeleteSuccess(res: any) {}
2583
- onDeleteError(err: Error) {}
2584
1358
  }
2585
1359
  ```
2586
1360
 
2587
1361
  ---
2588
1362
 
2589
- ## Technical Appendix: Redis Persistence & Snapshotting Guide
2590
-
2591
- For mission-critical data, you must configure how Redis saves your state to disk.
2592
-
2593
- ### 1. RDB (Redis Database)
2594
- - **Concept**: A point-in-time snapshot.
2595
- - **Config**: `save 900 1` (Save every 15 mins if 1 key changed).
2596
- - **BTS Soft Recommendation**: Use for secondary cache instances where a few minutes of data loss is acceptable.
2597
-
2598
- ### 2. AOF (Append Only File)
2599
- - **Concept**: Logs every write operation.
2600
- - **Config**: `appendfsync everysec`.
2601
- - **BTS Soft Recommendation**: Mandatory for the Redis instance handling **BullMQ Notification Queues**. This ensures that even in a crash, no job is lost.
2602
-
2603
- ### 3. Hybrid (RDB + AOF)
2604
- - **Concept**: Fast restarts with AOF durability.
2605
- - **BTS Soft Recommendation**: The "Gold Standard" for production.
2606
-
2607
- ---
2608
-
2609
- ## Technical Appendix: Notification Retry & Backoff Mathematics
2610
-
2611
- The `NotificationService` uses an exponential backoff formula for retries.
2612
-
2613
- ### The Formula
2614
- `delay = initialDelay * (multiplier ^ retryCount)`
2615
-
2616
- ### Standard Configuration
2617
- | Retry Count | Multiplier | Delay (Seconds) |
2618
- | :--- | :--- | :--- |
2619
- | 1 | 2 | 1.0 |
2620
- | 2 | 2 | 2.0 |
2621
- | 3 | 2 | 4.0 |
2622
- | 4 | 2 | 8.0 |
2623
- | 5 | 2 | 16.0 |
2624
-
2625
- ### Why Exponential?
2626
- Linear retries often cause "Rejection Loops" where the provider (Twilio/WhatsApp) keeps rejecting the same surge of traffic. Exponential backoff allows the provider's systems to breathe and clear bottlenecks before the next attempt.
2627
-
2628
- ---
2629
-
2630
- ## Technical Appendix: The Giant SQL Injection Pattern Wiki (Part 2)---
2631
-
2632
- ## Technical Appendix: Enterprise Support & Custom Development
2633
-
2634
- If your organization requires specialized support or custom module development within the `@bts-soft` ecosystem, we offer several enterprise-grade engagement models.
2635
-
2636
- ### 1. Dedicated Support SLA
2637
- - **Response Time**: Same-day response for critical (P0) infrastructure issues.
2638
- - **Consultancy**: Monthly architectural reviews of your microservices that utilize `@bts-soft/core`.
2639
- - **Custom Adapters**: Development of specialized notification channels (e.g., custom SMS gateways for specific MENA regions).
2640
-
2641
- ### 2. Security Hardening & Penetration Testing
2642
- - **Audit**: Biannual security audits of your codebase to ensure all `@bts-soft` decorators are correctly applied.
2643
- - **Reporting**: Detailed vulnerability reports and remediation strategies.
2644
-
2645
- ---
2646
-
2647
-
2648
- ## Epilogue: The BTS Soft Legacy
2649
- 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.
2650
-
2651
- ---
2652
-
2653
- ## Credits & License
2654
- Created and maintained by **Omar Sabry** for **BTS Soft**.
2655
- Licensed under the **MIT License**.
2656
- © 2026 BTS Soft. All Rights Reserved.
2657
-
2658
- ### 3. `@bts-soft/cache`
2659
-
2660
- #### `RedisService.ts`
2661
- - **Purpose**: High-performance caching wrapper.
2662
- - **Logic**: Handles JSON serialization, TTL management, and atomic operations.
2663
-
2664
- #### `CacheConfigService.ts`
2665
- - **Purpose**: Configuration provider for Redis connection strings.
2666
-
2667
- ---
2668
-
2669
- ### 4. `@bts-soft/upload`
2670
-
2671
- #### `UploadService.ts`
2672
- - **Purpose**: The main API for asset management.
2673
- - **Logic**: Executes `UploadImageCommand` or `DeleteImageCommand`.
2674
-
2675
- #### `CloudinaryUploadStrategy.ts`
2676
- - **Purpose**: Cloudinary-specific implementation.
2677
- - **Logic**: Stream-pipes files to `cloudinary.uploader.upload_stream`.
2678
-
2679
- #### `uploadImage.command.ts`
2680
- - **Purpose**: Command object for uploading images.
2681
- - **Logic**: Orchestrates the strategy and notifies observers.
2682
-
2683
- ---
2684
-
2685
- ### 5. `@bts-soft/common`
2686
-
2687
- #### `BaseEntity.ts`
2688
- - **Purpose**: Global abstract class for all TypeORM entities.
2689
- - **Logic**: Provides `id` (ULID), `createdAt`, `updatedAt`.
2690
-
2691
- #### `GeneralResponseInterceptor.ts`
2692
- - **Purpose**: Standardizes API output envelopes.
2693
- - **Logic**: Maps success and error states into a uniform JSON structure.
2694
-
2695
- #### `SqlInjectionInterceptor.ts`
2696
- - **Purpose**: Global request scrubber.
2697
- - **Logic**: Recursively walks request objects and executes regex checks.
2698
-
2699
- ---
2700
-
2701
- ## Technical Appendix: Global Event & Microservices Dictionary
2702
-
2703
- This section provides a complete map of every event emitted and consumed within the BTS Soft ecosystem.
2704
-
2705
- | Event ID | Emitted By | Payload | Consumer | Description |
2706
- | :--- | :--- | :--- | :--- | :--- |
2707
- | uth.user.login | AuthService | { userId, ip } | AuditLogService | Tracks login attempts. |
2708
- | uth.user.logout | AuthService | { userId } | RedisService | Invalidates session tokens. |
2709
- | cache.key.set | RedisService | { key, ttl } | MetricsService | Tracks cache fill rate. |
2710
- | cache.key.hit | RedisService | { key } | MetricsService | Tracks cache hit ratio. |
2711
- |
2712
- otif.queue.add | NotifService | { jobId } | BullMQ | Enqueues a notification. |
2713
- |
2714
- otif.job.start | WorkerService | { jobId } | LoggerService | Marks start of processing. |
2715
- |
2716
- otif.job.done | WorkerService | { jobId } | LoggerService | Success entry. |
2717
- |
2718
- otif.job.fail | WorkerService | { jobId, err } | ErrorHub | Triggers retry/alerting. |
2719
- | upload.local.tmp | UploadService | { path } | FileSystem | Local staging of file. |
2720
- | upload.cloud.req | CloudService | { provider } | MetricsService | Outbound API call tracking. |
2721
- | upload.asset.new | UploadService | { url } | DatabaseService | Permanent link storage. |
2722
-
2723
- ---
2724
-
2725
- ## Technical Appendix: SQL Injection Pattern Database (Giant Wiki Edition)
2726
-
2727
- ### Detailed Regex Walkthrough
2728
- Our regex /(\b(SELECT|INSERT...)/gi is optimized for V8 engine performance.
2729
-
2730
- | Part | Meaning | Performance Note |
2731
- | :--- | :--- | :--- |
2732
- | \b | Word Boundary | Prevents partial matches like 'SELECTIVE'. |
2733
- | SELECT | DML keyword | Case-insensitive match. |
2734
- | (--) | SQL Comment | Catches termination attempts. |
2735
- | \/\* | Multiline Com | Blocks bypass strategies. |
2736
-
2737
- ---
2738
-
2739
- ## Detailed Line-by-Line Code Audit (Part 3)
2740
-
2741
- ### GeneralResponseInterceptor.ts
2742
- - **Line 1-10**: Setup RxJS map operator.
2743
- - **Line 11-20**: Extract request context (REST vs GQL).
2744
- - **Line 21-30**: Structure the SuccessResponse object.
2745
- - **Line 31-40**: Inject server timestamp.
2746
- - **Line 41-50**: Execute the handleError fallback.
2747
-
2748
-
2749
- ## The 100-Question Technical FAQ Registry (Part 1/2)
2750
-
2751
- **Q1: How does the RedisService handle JSON serialization?**
2752
- A: It uses the native JSON.stringify and JSON.parse. Surfaces are typed as T using TypeScript generics to ensure that when you pull data out, it matches the interface you expect. This is handled centrally in the get and set methods to ensure consistency across the entire application ecosystem.
2753
-
2754
- **Q2: What is the benefit of using ULIDs over UUIDs in @bts-soft/common?**
2755
- A: ULIDs (Universally Unique Lexicographically Sortable Identifiers) are sortable by creation time. This is a critical advantage for database indexing (especially in TypeORM/PostgreSQL) because it prevents the high index fragmentation caused by random UUIDs, while still providing the same collision resistance.
2756
-
2757
- **Q3: How does the SQL injection regex detect 'Time-based' attacks?**
2758
- A: It looks for high-risk SQL functions like SLEEP(), WAITFOR DELAY, and BENCHMARK(). By blocking these strings in the SqlInjectionInterceptor, we prevent attackers from using timing differentials to infer data from the database even when error messages are suppressed.
2759
-
2760
- **Q4: Can I use the UploadService with local storage instead of Cloudinary?**
2761
- A: Yes. You must implement the IUploadStrategy interface and create a LocalStorageStrategy. Then, swap the injection in the UploadModule. The UploadService is entirely decoupled from the actual storage implementation thanks to the Strategy Pattern.
2762
-
2763
- **Q5: What happens if a notification job fails in BullMQ?**
2764
- A: The job is moved to the 'delayed' state and retried using an exponential backoff strategy (default: 3 retries, starting at 5s). After all retries are exhausted, it is moved to the 'failed' set, where it can be inspected via tools like Bull-Board for manual intervention or debugging.
2765
-
2766
- **Q6: Is the GeneralResponseInterceptor compatible with GraphQL?**
2767
- A: Yes. The interceptor detects the request context. For GraphQL, it allows errors to pass through as GraphQLError objects (with correct extensions), while for REST it wraps responses in the success/data/items envelope. It is designed to provide a unified API experience across both protocols.
2768
-
2769
- **Q7: How do I configure multiple email senders?**
2770
- A: The EmailChannel uses a single SMTP configuration. To support multiple senders, you can override the rom field in the channelOptions of the
2771
- otificationService.send call. Note that your SMTP provider (e.g., SendGrid) must allow the secondary email as a verified sender.
2772
-
2773
- **Q8: Does the WhatsApp channel support document attachments?**
2774
- A: Yes. Pass the mediaUrl array in the channelOptions. Twilio will then fetch the media from that URL and send it as a WhatsApp message. Supported formats include PDF, DOCX, and common image formats.
2775
-
2776
- **Q9: How do I implement "Sliding Window" rate limiting?**
2777
- A: Use the ThrottlerModule from @bts-soft/common. It is pre-configured to use Redis as the storage backend, ensuring that rate limits are enforced across all horizontal instances of your microservice, rather than just per-process.
2778
-
2779
- **Q10: What is the P99 latency of the RedisService.get method?**
2780
- A: In our benchmarks on AWS (c5.large instances) with a co-located Redis ElastiCache instance, P99 latency is consistently below 1.2ms for keys under 10KB. This makes it suitable for high-frequency sub-millisecond data retrieval.
2781
-
2782
-
2783
- ## Internal Logic Architecture Wiki: Deep Class Registry
2784
-
2785
- This section provide a line-by-line architectural analysis of the core classes in the @bts-soft/core monorepo.
2786
-
2787
- ---
2788
-
2789
- ### 1. @bts-soft/validation - Core Classes
2790
-
2791
- #### BaseFieldDecorator (Abstract)
2792
- - **Lines of Code**: ~50
2793
- - **Purpose**: Provides a unified interface for all custom field decorators.
2794
- - **Key Methods**:
2795
- - pplyValidators(): Composes the array of class-validator decorators.
2796
- - pplyTransformers(): Composes the array of class-transformer operators.
2797
- - **Architectural Note**: This class ensures that all decorators maintain a consistent 'security-first' posture by enforcing SQL injection checks at the base level.
2798
-
2799
- #### EmailFieldDecorator (Implementation)
2800
- - **Lines of Code**: ~35
2801
- - **Purpose**: Specialized logic for email address sanitization.
2802
- - **Key Logic**:
2803
- - oLower(): Ensures that all emails are stored and compared in lowercase to prevent case-sensitivity bugs.
2804
- - IsEmail(): Standard RFC compliance check.
2805
- - **Security Note**: This is part of the 'Critical Data Pathway' and has a 100% test coverage requirement.
2806
-
2807
- #### PasswordFieldDecorator (Implementation)
2808
- - **Lines of Code**: ~60
2809
- - **Purpose**: Enforces the enterprise security policy for passwords.
2810
- - **Policy Details**:
2811
- - Minimum 8 characters.
2812
- - Maximum 64 characters (to prevent Deny-of-Service attacks on bcrypt).
2813
- - One non-alphanumeric character required.
2814
- - **Logic Flow**: Rejects common passwords via a lookup table (if enabled) and validates complexity.
2815
-
2816
- #### NationalIdDecorator (Implementation)
2817
- - **Lines of Code**: ~40
2818
- - **Domain**: Egyptian National Identity.
2819
- - **Logic**:
2820
- - Digit 1: Century (2 for 1900-1999, 3 for 2000-2099).
2821
- - Digits 2-7: Date of Birth.
2822
- - Digits 8-13: Serial number.
2823
- - Digit 14: Check digit.
2824
- - **Implementation**: Uses a complex regex and a transform function to strip non-numeric characters.
2825
-
2826
- ---
2827
-
2828
- ### 2. @bts-soft/notifications - Core Classes
2829
-
2830
- #### NotificationChannelFactory
2831
- - **Lines of Code**: ~110
2832
- - **Purpose**: Central hub for creating notification channel instances.
2833
- - **Technical Logic**:
2834
- - Uses the ChannelType enum to determine the target strategy.
2835
- - Injects ConfigService to retrieve API keys securely.
2836
- - Returns a singleton instance for channels that are thread-safe (like Email).
2837
-
2838
- #### EmailChannel
2839
- - **Lines of Code**: ~150
2840
- - **Purpose**: Nodemailer bridge.
2841
- - **Implementation Details**:
2842
- - Supports multiple transport protocols (SMTP, AWS SES, Mailgun).
2843
- - Handles HTML and Plain Text versions of messages.
2844
- - Provides detailed error logging via the Logger service.
1363
+ ## Security Model
2845
1364
 
2846
- #### BullMQWorkerService
2847
- - **Lines of Code**: ~200
2848
- - **Purpose**: High-performance consumer for the notification queue.
2849
- - **Logic Flow**:
2850
- 1. Pulls job from Redis.
2851
- 2. Identifies the correct channel.
2852
- 3. Executes the send() method.
2853
- 4. Updates the job state (Done/Failed).
2854
- - **Metric Tracking**: Reports job processing time to our internal Prometheus dashboard.
1365
+ ### Defense in Depth
2855
1366
 
1367
+ ```
1368
+ Internet Traffic
1369
+ │
1370
+ ▼
1371
+ ThrottlingModule (rate limiting — DDoS / brute force protection)
1372
+ │
1373
+ ▼
1374
+ SqlInjectionInterceptor (recursive payload scanning — SQLi / stacked queries)
1375
+ │
1376
+ ▼
1377
+ ValidationPipe (DTO constraints + auto-transformation)
1378
+ │
1379
+ ▼
1380
+ DistributedLockService (critical section guard — race condition prevention)
1381
+ │
1382
+ ▼
1383
+ Business Logic
1384
+ │
1385
+ ▼
1386
+ ClassSerializerInterceptor (strip @Exclude() fields — prevent data leakage)
1387
+ │
1388
+ ▼
1389
+ Client Response
1390
+ ```
2856
1391
 
2857
- ## Technical Audit: Internal Service Logic (Line-by-Line)
2858
-
2859
- This section provides an exhaustive walk-through of the critical source code within @bts-soft/core.
2860
-
2861
- ---
2862
-
2863
- ### 1. RedisService.ts Audit
2864
-
2865
- | Line | Code | Technical Analysis |
1392
+ | Layer | Mechanism | Protection Against |
2866
1393
  | :--- | :--- | :--- |
2867
- | 1 | import { Injectable, Logger } from '@nestjs/common'; | Standard NestJS dependency injection and logging. |
2868
- | 2 | import { Redis } from 'ioredis'; | The underlying high-performance Redis client. |
2869
- | ... | ... | ... |
2870
- | 25 | public async get<T>(key: string): Promise<T | null> { | Entry point for typed data retrieval. |
2871
- | 26 | const data = await this.client.get(key); | Raw I/O call to the Redis cluster. |
2872
- | 27 | if (!data) return null; | Handle non-existent keys defensively. |
2873
- | 28 | try { return JSON.parse(data) as T; } | Attempt deserialization of potential objects. |
2874
- | 29 | catch { return data as unknown as T; } | Fallback to raw string if JSON parsing fails. |
2875
- | 30 | } | Method termination. |
2876
-
2877
- ---
1394
+ | Network | `ThrottlingModule` | DDoS, brute force, noisy neighbor attacks |
1395
+ | Payload | `SqlInjectionInterceptor` | SQL injection, stacked queries, time-delay attacks, xp_cmdshell |
1396
+ | Input | `ValidationPipe` with composite decorators | Type coercion attacks, invalid data formats |
1397
+ | Concurrency | `DistributedLockService` | Race conditions across service replicas |
1398
+ | Output | `ClassSerializerInterceptor` | Sensitive field exposure (passwords, internal IDs) |
1399
+ | Files | `FileValidatorService` | Extension whitelist bypass, disk exhaustion via oversized uploads |
2878
1400
 
2879
- ### 2. NotificationService.ts Audit
1401
+ ### Production Console Silencing
2880
1402
 
2881
- | Line | Code | Technical Analysis |
2882
- | :--- | :--- | :--- |
2883
- | 45 | public async send(type: ChannelType, msg: IMessage): Promise<void> { | Main entry point for the Notification dispatcher. |
2884
- | 46 | const start = Date.now(); | Performance tracking for the BullMQ payload. |
2885
- | 47 | await this.notificationQueue.add('sendMail', { type, msg }); | Atomically enqueues the job into Redis. |
2886
- | 48 | this.logger.log(Job queued in ms); | Log the overhead of the queueing operation (p99 < 5ms). |
2887
- | 49 | } | End of method. |
1403
+ When `NODE_ENV=production`, all `console.*` methods are automatically no-ops after `setupInterceptors(app)` is called. This prevents accidental logging of tokens, passwords, or PII in production. Use NestJS `Logger` for structured production logging instead.
2888
1404
 
2889
1405
  ---
2890
1406
 
2891
- ### 3. GeneralResponseInterceptor.ts Audit
1407
+ ## Environment Variable Reference
2892
1408
 
2893
- | Line | Code | Technical Analysis |
2894
- | :--- | :--- | :--- |
2895
- | 15 | intercept(context: ExecutionContext, next: CallHandler): Observable<any> { | Implement the NestJS Interceptor interface. |
2896
- | 16 | const request = context.switchToHttp().getRequest(); | Extract the HTTP request object. |
2897
- | 17 | return next.handle().pipe( | Start the RxJS processing pipeline. |
2898
- | 18 | map((data) => ({ | Map raw data into the standard BTS Soft envelope. |
2899
- | 19 | success: true, | Set global success flag. |
2900
- | 20 | statusCode: context.switchToHttp().getResponse().statusCode, | Sync status code with the response object. |
2901
- | 21 | data, | Place payload in the data field. |
2902
- | 22 | timeStamp: new Date().toISOString(), | Add server-side ISO timestamp. |
2903
- | 23 | })), | Close the map function. |
2904
- | 24 | ); | Close the pipeline. |
1409
+ ### Redis
2905
1410
 
2906
- ---
1411
+ | Variable | Default | Required | Description |
1412
+ | :--- | :--- | :--- | :--- |
1413
+ | `REDIS_HOST` | `localhost` | No | Redis hostname |
1414
+ | `REDIS_PORT` | `6379` | No | Redis port |
1415
+ | `REDIS_PASSWORD` | — | No | Authentication password |
1416
+ | `REDIS_DB` | `0` | No | Database index (0-15) |
1417
+ | `REDIS_TTL` | `3600` | No | Default cache TTL in seconds |
2907
1418
 
2908
- ### 4. UploadService.ts Audit
1419
+ ### Notifications
2909
1420
 
2910
- | Line | Code | Technical Analysis |
1421
+ | Variable | Description |
1422
+ | :--- | :--- |
1423
+ | `EMAIL_HOST`, `EMAIL_PORT`, `EMAIL_USER`, `EMAIL_PASS`, `EMAIL_SENDER` | SMTP configuration |
1424
+ | `EMAIL_PROVIDER` | `nodemailer`, `twilio-mail`, or `ses` |
1425
+ | `SENDGRID_API_KEY` | SendGrid API key |
1426
+ | `AWS_SES_ACCESS_KEY_ID`, `AWS_SES_SECRET_ACCESS_KEY`, `AWS_SES_REGION` | Amazon SES credentials |
1427
+ | `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, `TWILIO_SMS_NUMBER`, `TWILIO_WHATSAPP_NUMBER` | Twilio credentials |
1428
+ | `SMS_PROVIDER` | `twilio`, `smsmisr`, or `vonage` |
1429
+ | `SMSMISR_USERNAME`, `SMSMISR_PASSWORD`, `SMSMISR_SENDER` | SMS Misr credentials |
1430
+ | `VONAGE_API_KEY`, `VONAGE_API_SECRET`, `VONAGE_SENDER` | Vonage credentials |
1431
+ | `TELEGRAM_BOT_TOKEN` | Telegram Bot API token |
1432
+ | `DISCORD_WEBHOOK_URL` | Discord default webhook URL |
1433
+ | `TEAMS_WEBHOOK_URL` | Microsoft Teams webhook URL |
1434
+ | `SLACK_WEBHOOK_URL`, `SLACK_BOT_TOKEN`, `SLACK_DEFAULT_CHANNEL` | Slack integration |
1435
+ | `FB_PAGE_ACCESS_TOKEN`, `FB_GRAPH_API_VERSION` | Facebook Messenger (default: `v18.0`) |
1436
+ | `FIREBASE_SERVICE_ACCOUNT_PATH` | Path to Firebase Admin SDK JSON file |
1437
+ | `ONESIGNAL_APP_ID`, `ONESIGNAL_REST_API_KEY` | OneSignal credentials |
1438
+ | `WEB_PUSH_PUBLIC_KEY`, `WEB_PUSH_PRIVATE_KEY`, `WEB_PUSH_SUBJECT` | VAPID keys for Web Push |
1439
+ | `IN_APP_PROVIDER` | `pusher` |
1440
+ | `PUSHER_APP_ID`, `PUSHER_KEY`, `PUSHER_SECRET`, `PUSHER_CLUSTER` | Pusher Channels credentials |
1441
+ | `WEBHOOK_DEFAULT_SIGNING_SECRET` | Webhook payload signing secret |
1442
+
1443
+ ### Upload
1444
+
1445
+ | Variable | Default | Description |
2911
1446
  | :--- | :--- | :--- |
2912
- | 10 | @Injectable() | Marks class for singleton injection. |
2913
- | 11 | export class UploadService { | Main class definition. |
2914
- | 12 | constructor(private strategy: CloudinaryStrategy) {} | Injects the storage strategy via the constructor. |
2915
- | 13 | async upload(file: any): Promise<UploadResult> { | Orchestrates the upload command and strategy. |
2916
- | 14 | return await this.strategy.upload(file); | Direct delegation to the chosen strategy. |
1447
+ | `UPLOAD_PROVIDER` | `local` | `local` or `cloudinary` |
1448
+ | `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, `CLOUDINARY_API_SECRET` | — | Cloudinary credentials |
1449
+ | `UPLOAD_LOCAL_PATH` | `./uploads` | Local storage directory path |
1450
+ | `UPLOAD_MAX_IMAGE_SIZE` | `5242880` | Max image size in bytes (5 MB) |
1451
+ | `UPLOAD_MAX_VIDEO_SIZE` | `104857600` | Max video size in bytes (100 MB) |
1452
+ | `UPLOAD_MAX_AUDIO_SIZE` | `52428800` | Max audio size in bytes (50 MB) |
1453
+ | `UPLOAD_MAX_FILE_SIZE` | `10485760` | Max raw file size in bytes (10 MB) |
1454
+ | `UPLOAD_MAX_MODEL_3D_SIZE` | `104857600` | Max 3D model size in bytes (100 MB) |
1455
+ | `UPLOAD_RATE_LIMIT_CAPACITY` | `10` | Token bucket capacity for chunked uploads |
1456
+ | `UPLOAD_RATE_LIMIT_REFILL_RATE` | `2` | Tokens refilled per second |
2917
1457
 
2918
1458
  ---
2919
1459
 
1460
+ ## Testing and Verification
2920
1461
 
2921
- ## Technical Audit: Internal Service Logic (Exhaustive Part 2)
2922
-
2923
- ### 1. RedisService.ts Deep Walkthrough
1462
+ Each sub-package ships with its own unit and end-to-end test suites. Unit tests use mocked dependencies; E2E tests target real infrastructure via Docker Compose.
2924
1463
 
2925
- | Line | Component | Purpose | Technical Summary |
1464
+ | Package | Unit Tests | E2E Tests | Infrastructure Required |
2926
1465
  | :--- | :--- | :--- | :--- |
2927
- | 1 | Header | Licensing & Credits | Copyright BTS Soft 2026. |
2928
- | 2 | Imports | Core Dependencies | NestJS Common, IOredis, Cache-Manager. |
2929
- | 3 | Metadata | Service Identity | Decorated with @Injectable. |
2930
- | 4 | Class | RedisService | Main singleton definition. |
2931
- | 5 | Prop | client | The ioredis connection instance. |
2932
- | 6 | Prop | logger | Standard NestJS logging hub. |
2933
- | 7 | Constructor | Init Logic | Dependency injection of cache manager. |
2934
- | 8 | Init | Log | Prints 'Redis Service Initialized' on boot. |
2935
- | ... | ... | ... | ... |
2936
- | 100 | Get | Logic | Reads key from Redis memory. |
2937
- | 101 | Get | If | Checks if key exists. |
2938
- | 102 | Get | Parse | Converts string to JSON object. |
2939
- | 103 | Get | Catch | Error handling for malformed JSON. |
2940
- | 104 | Get | Return | Returns T or null. |
1466
+ | `@bts-soft/validation` | Yes | Yes | None (mocked NestJS application) |
1467
+ | `@bts-soft/cache` | Yes (ioredis-mock) | Yes | Redis server on port 6380 |
1468
+ | `@bts-soft/notifications` | Yes | Yes | Redis via Docker Compose |
1469
+ | `@bts-soft/upload` | Yes | Yes | None (filesystem mocks) |
1470
+ | `@bts-soft/common` | Yes | Yes | PostgreSQL and Redis via Docker Compose |
2941
1471
 
2942
- ---
2943
-
2944
- ### 2. NotificationService.ts Deep Walkthrough
2945
-
2946
- | Line | Component | Purpose | Technical Summary |
2947
- | :--- | :--- | :--- | :--- |
2948
- | 1 | Header | Licensing | BTS Soft proprietary code. |
2949
- | 2 | Imports | Queue & Strategy | BullMQ, Factory, Internal Interfaces. |
2950
- | 3 | Class | NotificationService | The main dispatcher class. |
2951
- | 4 | Constructor | Setup | Injecting the BullMQ 'notifications' queue. |
2952
- | 5 | Method | send() | High-level API for all messaging. |
2953
- | 6 | Method | sendEmail() | Proxies to EmailChannel provider. |
2954
- | 7 | Method | sendSms() | Proxies to Twilio provider. |
2955
- | 8 | Method | sendWhatsapp() | Proxies to Twilio WA provider. |
2956
- | 9 | Method | sendFirebase() | Proxies to FCM provider. |
2957
- | 10 | Method | sendTelegram() | Proxies to Bot API provider. |
2958
- | 11 | Method | sendDiscord() | Proxies to Webhook provider. |
2959
- | 12 | Method | sendTeams() | Proxies to Webhook provider. |
2960
- | 13 | Method | sendMessenger() | Proxies to Graph API provider. |
1472
+ Running tests in each package directory:
2961
1473
 
2962
- ---
1474
+ ```bash
1475
+ # Unit tests
1476
+ npm run test
2963
1477
 
2964
- ### 3. GeneralResponseInterceptor.ts Deep Walkthrough
1478
+ # Coverage report
1479
+ npm run test:cov
2965
1480
 
2966
- | Line | Component | Purpose | Technical Summary |
2967
- | :--- | :--- | :--- | :--- |
2968
- | 40 | Pipe | Start | Begins the RxJS pipeline processing. |
2969
- | 41 | Map | Structure | Defines the shared JSON envelope. |
2970
- | 42 | Key | Success | Hardcoded boolean true on success. |
2971
- | 43 | Key | StatusCode | Mirroring the HTTP status from context. |
2972
- | 44 | Key | Message | Defaults to 'Request successful'. |
2973
- | 45 | Key | Data | The payload from the controller. |
2974
- | 46 | Key | Items | Optional list field for arrays. |
2975
- | 47 | Key | Pagination | Metadata for lists (total, page, limit). |
2976
- | 48 | Key | TimeStamp | ISO string from current system clock. |
1481
+ # End-to-end integration tests
1482
+ npm run test:e2e
1483
+ ```
2977
1484
 
2978
1485
  ---
2979
1486
 
2980
- ### 4. UploadService.ts Deep Walkthrough
2981
-
2982
- | Line | Component | Purpose | Technical Summary |
2983
- | :--- | :--- | :--- | :--- |
2984
- | 1 | Header | Licensing | BTS Soft 2026. |
2985
- | 2 | Imports | Command Pattern | IUploadCommand, IUploadStrategy. |
2986
- | 3 | Class | UploadService | Facade for asset management. |
2987
- | 4 | Method | upload() | Executes the 'Upload' logic flow. |
2988
- | 5 | Method | delete() | Executes the 'Delete' logic flow. |
2989
- | 6 | Hook | onSuccess | Reactive callback for completion. |
2990
- | 7 | Hook | onError | Reactive callback for failures. |
1487
+ ## FAQ
2991
1488
 
1489
+ **Can I install individual sub-packages instead of @bts-soft/core?**
1490
+ Yes. Each sub-package is independently published on npm and can be installed in isolation. `@bts-soft/core` is a convenience wrapper for when you need the Back-End.
2992
1491
 
2993
- ---
1492
+ **Does @bts-soft/core support NestJS 10?**
1493
+ No. Version 3.x requires NestJS 11 or later due to peer dependency alignment across all sub-packages.
2994
1494
 
2995
- ## Technical Appendix: High-Availability (HA) Tuning Guide for MENA Region
1495
+ **How do I switch from Cloudinary to local storage?**
1496
+ Set `UPLOAD_PROVIDER=local` in your environment. No code changes are required.
2996
1497
 
2997
- When deploying @bts-soft/core in the Middle East & North Africa (MENA), specific network characteristics and peering issues must be addressed.
1498
+ **What happens if Redis is unavailable?**
1499
+ The rate limiter in `@bts-soft/validation` automatically falls back to in-memory storage with a warning log. The `RedisService` in `@bts-soft/cache` will throw on all operation calls; wrap cache calls in `try/catch` and implement a circuit breaker if the application must remain functional without the cache layer.
2998
1500
 
2999
- ### Network Profile 1: Low-Latency Redis Cluster (GCC Regions)
3000
- - **Primary Host**: Bahrain (AWS)
3001
- - **Replica Nodes**: UAE (Azure), Saudi (Google Cloud)
3002
- - **Tuning Strategy**:
3003
- - cp_keepalive: 30 seconds.
3004
- - client_timeout: 500ms.
3005
- - **Reasoning**: Intra-GCC fiber is fast, but cross-cloud peering can be jittery.
1501
+ **Can I use @bts-soft/common's ORM adapters without a specific ORM installed?**
1502
+ Yes. The ORM adapters are exposed through sub-path exports such as `@bts-soft/common/typeorm`, `/mongoose`, `/sequelize`, and `/prisma`. The main entry point does not require any specific ORM at runtime. Only the sub-path you explicitly import is loaded.
3006
1503
 
3007
- ### Network Profile 2: High-Durability SMTP (North Africa)
3008
- - **Primary Host**: Damietta
3009
- - **Gateway**: SendGrid (Relay via London)
3010
- - **Tuning Strategy**:
3011
- - dns_lookup: Prefer IPv4 (Avoid IPv6 propagation lag).
3012
- - smtp_timeout: 10,000ms.
3013
- - **Reasoning**: Residential ISPs in Northern Africa often have legacy DNS resolvers.
1504
+ **How does the notification rate limiter interact with the validation rate limiter?**
1505
+ They are independent systems. The notification rate limiter operates inside the notification pre-flight pipeline using Redis sorted sets with a sliding window per recipient per channel. The validation `@RateLimit` decorator operates at the NestJS guard level on incoming HTTP or GraphQL requests.
3014
1506
 
3015
- [... Generating Profiler 3 to 20 with similar technical detail ...]
1507
+ **Is there a way to extend the notification system with a new channel?**
1508
+ Yes. Implement the channel interface and register it with the channel factory. The pre-flight pipeline and BullMQ processor automatically route to your channel based on the `ChannelType` value.
3016
1509
 
3017
1510
  ---
3018
1511
 
3019
- ## Support
3020
-
3021
- ### Documentation
3022
- - Complete API documentation available for each package
3023
- - Examples and tutorials in GitHub repositories
3024
- - TypeScript definitions for all exports
3025
-
3026
- ### Community
3027
- - GitHub Issues for bug reports and feature requests
3028
- - Regular updates and maintenance
3029
- - Active development and support
3030
-
3031
- ### Enterprise
3032
- - Dedicated support available
3033
- - Custom implementations and consulting
3034
- - Training and integration services
3035
-
3036
1512
  ## License
3037
1513
 
3038
- All packages are licensed under the MIT License unless otherwise specified. Developed and maintained by BTS Soft.
1514
+ This project is licensed under the MIT License. See the [license](./license) file for details.
3039
1515
 
3040
- ## Contact
1516
+ ---
3041
1517
 
3042
- **Author:** Omar Sabry
3043
- **Email:** [omar.sabry.dev@gmail.com](mailto:omar.sabry.dev@gmail.com)
3044
- **LinkedIn:** [Omar Sabry](https://www.linkedin.com/in/omarsa6ry/)
3045
- **Portfolio:** [https://omarsabry.netlify.app/](https://omarsabry.netlify.app/)
1518
+ ## Author
3046
1519
 
3047
- ## Repository
1520
+ **Omar Sabry**
3048
1521
 
3049
- **GitHub Organization For All BTS Packages:** [Omar-Sa6ry/bts-soft: BTS Soft provides a comprehensive suite of professional, enterprise-grade Node.js packages designed for building scalable, maintainable backend systems with NestJS. Our packages are production-ready, thoroughly tested, and follow industry best practices.](https://github.com/Omar-Sa6ry/bts-soft)
1522
+ - Portfolio: [omarsabry.vercel.app](https://omarsabry.vercel.app/)
1523
+ - GitHub: [Omar-Sa6ry](https://github.com/Omar-Sa6ry)
1524
+ - npm: [@bts-soft](https://www.npmjs.com/~bts-soft)