@flusys/nestjs-shared 1.0.0-rc → 1.0.1

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.
Files changed (63) hide show
  1. package/README.md +493 -658
  2. package/cjs/classes/api-service.class.js +59 -92
  3. package/cjs/classes/winston-logger-adapter.class.js +23 -40
  4. package/cjs/constants/permissions.js +11 -1
  5. package/cjs/dtos/delete.dto.js +10 -0
  6. package/cjs/dtos/response-payload.dto.js +0 -75
  7. package/cjs/guards/permission.guard.js +19 -18
  8. package/cjs/interceptors/index.js +0 -3
  9. package/cjs/interceptors/set-user-field-on-body.interceptor.js +20 -3
  10. package/cjs/middlewares/logger.middleware.js +50 -89
  11. package/cjs/modules/datasource/datasource.module.js +11 -14
  12. package/cjs/modules/datasource/multi-tenant-datasource.service.js +0 -4
  13. package/cjs/modules/utils/utils.service.js +22 -103
  14. package/cjs/utils/error-handler.util.js +12 -67
  15. package/cjs/utils/html-sanitizer.util.js +10 -20
  16. package/cjs/utils/index.js +2 -0
  17. package/cjs/utils/request.util.js +70 -0
  18. package/cjs/utils/string.util.js +63 -0
  19. package/classes/api-service.class.d.ts +2 -0
  20. package/classes/winston-logger-adapter.class.d.ts +2 -0
  21. package/constants/permissions.d.ts +12 -0
  22. package/dtos/delete.dto.d.ts +1 -0
  23. package/dtos/response-payload.dto.d.ts +0 -13
  24. package/fesm/classes/api-service.class.js +59 -92
  25. package/fesm/classes/winston-logger-adapter.class.js +23 -40
  26. package/fesm/constants/permissions.js +8 -1
  27. package/fesm/dtos/delete.dto.js +12 -2
  28. package/fesm/dtos/response-payload.dto.js +0 -69
  29. package/fesm/guards/permission.guard.js +19 -18
  30. package/fesm/interceptors/index.js +0 -3
  31. package/fesm/interceptors/set-user-field-on-body.interceptor.js +3 -0
  32. package/fesm/middlewares/logger.middleware.js +50 -83
  33. package/fesm/modules/datasource/datasource.module.js +11 -14
  34. package/fesm/modules/datasource/multi-tenant-datasource.service.js +0 -4
  35. package/fesm/modules/utils/utils.service.js +19 -89
  36. package/fesm/utils/error-handler.util.js +12 -68
  37. package/fesm/utils/html-sanitizer.util.js +1 -14
  38. package/fesm/utils/index.js +2 -0
  39. package/fesm/utils/request.util.js +58 -0
  40. package/fesm/utils/string.util.js +71 -0
  41. package/guards/permission.guard.d.ts +2 -0
  42. package/interceptors/index.d.ts +0 -3
  43. package/interceptors/set-user-field-on-body.interceptor.d.ts +3 -0
  44. package/interfaces/logged-user-info.interface.d.ts +0 -2
  45. package/middlewares/logger.middleware.d.ts +2 -2
  46. package/modules/datasource/datasource.module.d.ts +1 -0
  47. package/modules/datasource/multi-tenant-datasource.service.d.ts +0 -1
  48. package/modules/utils/utils.service.d.ts +2 -18
  49. package/package.json +2 -2
  50. package/utils/error-handler.util.d.ts +3 -18
  51. package/utils/html-sanitizer.util.d.ts +0 -1
  52. package/utils/index.d.ts +2 -0
  53. package/utils/request.util.d.ts +4 -0
  54. package/utils/string.util.d.ts +2 -0
  55. package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -12
  56. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -12
  57. package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -12
  58. package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -4
  59. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -4
  60. package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -4
  61. package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -1
  62. package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -1
  63. package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -1
package/README.md CHANGED
@@ -8,9 +8,9 @@ This comprehensive guide covers the shared package - the shared NestJS infrastru
8
8
  ## Table of Contents
9
9
 
10
10
  - [Overview](#overview)
11
- - [Installation](#installation)
12
11
  - [Package Architecture](#package-architecture)
13
12
  - [ApiService - Generic CRUD Service](#apiservice---generic-crud-service)
13
+ - [RequestScopedApiService](#requestscopedapiservice)
14
14
  - [ApiController - Generic CRUD Controller](#apicontroller---generic-crud-controller)
15
15
  - [Decorators](#decorators)
16
16
  - [Guards](#guards)
@@ -20,7 +20,9 @@ This comprehensive guide covers the shared package - the shared NestJS infrastru
20
20
  - [Multi-Tenant DataSource](#multi-tenant-datasource)
21
21
  - [DTOs](#dtos)
22
22
  - [Base Entities](#base-entities)
23
+ - [Utilities](#utilities)
23
24
  - [Error Handling](#error-handling)
25
+ - [Constants](#constants)
24
26
  - [API Reference](#api-reference)
25
27
 
26
28
  ---
@@ -30,34 +32,26 @@ This comprehensive guide covers the shared package - the shared NestJS infrastru
30
32
  `@flusys/nestjs-shared` provides shared utilities for building scalable NestJS applications:
31
33
 
32
34
  - **Generic CRUD** - Standardized API controller and service patterns
33
- - **Permission System** - Role and permission-based access control
34
- - **Caching** - In-memory + Redis hybrid caching
35
+ - **Permission System** - Role and permission-based access control with complex logic
36
+ - **Caching** - In-memory + Redis hybrid caching (HybridCache)
35
37
  - **Request Correlation** - AsyncLocalStorage-based request tracking
36
38
  - **Middleware** - Logging, correlation, and performance monitoring
37
39
  - **Interceptors** - Response metadata, idempotency, auto field setting
38
40
  - **Multi-Tenancy** - Dynamic database connection management
39
- - **Error Handling** - Centralized error handling utilities
41
+ - **Error Handling** - Centralized error handling with sensitive data redaction
40
42
 
41
43
  ### Package Hierarchy
42
44
 
43
45
  ```
44
- @flusys/nestjs-core Pure TypeScript (foundation)
45
-
46
- @flusys/nestjs-shared Shared NestJS utilities (THIS PACKAGE)
47
-
48
- @flusys/nestjs-auth Uses common classes
49
-
50
- @flusys/nestjs-iam Uses common patterns
51
-
52
- @flusys/nestjs-storage Uses common patterns
53
- ```
54
-
55
- ---
56
-
57
- ## Installation
58
-
59
- ```bash
60
- npm install @flusys/nestjs-shared @flusys/nestjs-core
46
+ @flusys/nestjs-core <- Pure TypeScript (foundation)
47
+ |
48
+ @flusys/nestjs-shared <- Shared NestJS utilities (THIS PACKAGE)
49
+ |
50
+ @flusys/nestjs-auth <- Uses common classes
51
+ |
52
+ @flusys/nestjs-iam <- Uses common patterns
53
+ |
54
+ @flusys/nestjs-storage <- Uses common patterns
61
55
  ```
62
56
 
63
57
  ---
@@ -73,17 +67,19 @@ nestjs-shared/
73
67
  │ │ ├── request-scoped-api.service.ts # REQUEST-scoped service base
74
68
  │ │ ├── hybrid-cache.class.ts # Two-tier caching
75
69
  │ │ ├── winston.logger.class.ts # Winston logger config
76
- │ │ ├── winston-logger-adapter.class.ts # Logger adapters
77
- │ │ └── index.ts
70
+ │ │ ├── winston-logger-adapter.class.ts
71
+ │ │ └── nest-logger-adapter.class.ts
78
72
  │ │
79
73
  │ ├── constants/ # Injection tokens & constants
74
+ │ │ ├── permissions.ts # Permission constants
80
75
  │ │ └── index.ts
81
76
  │ │
82
77
  │ ├── decorators/ # Custom decorators
83
- │ │ ├── api-response.decorator.ts # Swagger response decorator
84
- │ │ ├── current-user.decorator.ts
85
- │ │ ├── public.decorator.ts
86
- │ │ ├── require-permission.decorator.ts
78
+ │ │ ├── api-response.decorator.ts # @ApiResponseDto
79
+ │ │ ├── current-user.decorator.ts # @CurrentUser
80
+ │ │ ├── public.decorator.ts # @Public
81
+ │ │ ├── require-permission.decorator.ts # @RequirePermission
82
+ │ │ ├── sanitize.decorator.ts # @SanitizeHtml, @SanitizeAndTrim
87
83
  │ │ └── index.ts
88
84
  │ │
89
85
  │ ├── dtos/ # Shared DTOs
@@ -95,18 +91,16 @@ nestjs-shared/
95
91
  │ │ └── index.ts
96
92
  │ │
97
93
  │ ├── entities/ # Base entities
98
- │ │ ├── identity.ts
99
- │ │ ├── user-root.ts
94
+ │ │ ├── identity.ts # Base entity with UUID
95
+ │ │ ├── user-root.ts # Base user entity
100
96
  │ │ └── index.ts
101
97
  │ │
102
98
  │ ├── exceptions/ # Custom exceptions
103
- │ │ ├── permission.exception.ts
104
- │ │ └── index.ts
99
+ │ │ └── permission.exception.ts # Permission-related exceptions
105
100
  │ │
106
101
  │ ├── guards/ # Authentication & authorization
107
- │ │ ├── jwt-auth.guard.ts # JWT token validation
108
- │ │ ├── permission.guard.ts # Permission checks
109
- │ │ └── index.ts
102
+ │ │ ├── jwt-auth.guard.ts # JWT token validation
103
+ │ │ └── permission.guard.ts # Permission checks
110
104
  │ │
111
105
  │ ├── interceptors/ # Request/response interceptors
112
106
  │ │ ├── delete-empty-id-from-body.interceptor.ts
@@ -116,35 +110,34 @@ nestjs-shared/
116
110
  │ │ ├── set-create-by-on-body.interceptor.ts
117
111
  │ │ ├── set-delete-by-on-body.interceptor.ts
118
112
  │ │ ├── set-update-by-on-body.interceptor.ts
119
- │ │ ├── slug.interceptor.ts
120
- │ │ └── index.ts
113
+ │ │ └── slug.interceptor.ts
121
114
  │ │
122
115
  │ ├── interfaces/ # TypeScript interfaces
123
- │ │ ├── api.interface.ts
116
+ │ │ ├── api.interface.ts # IService interface
124
117
  │ │ ├── identity.interface.ts
125
- │ │ ├── logged-user-info.interface.ts
126
- │ │ ├── logger.interface.ts
127
- │ │ ├── permission.interface.ts
128
- │ │ └── index.ts
118
+ │ │ ├── logged-user-info.interface.ts # ILoggedUserInfo
119
+ │ │ ├── logger.interface.ts # ILogger
120
+ │ │ ├── permission.interface.ts # PermissionCondition
121
+ │ │ └── datasource-provider.interface.ts
129
122
  │ │
130
123
  │ ├── middlewares/ # Middleware
131
- │ │ ├── logger.middleware.ts # Request logging & correlation
132
- │ │ └── index.ts
124
+ │ │ └── logger.middleware.ts # Request logging & correlation
133
125
  │ │
134
126
  │ ├── modules/ # NestJS modules
135
127
  │ │ ├── cache/cache.module.ts
136
128
  │ │ ├── datasource/
137
129
  │ │ │ ├── datasource.module.ts
138
- │ │ │ ├── multi-tenant-datasource.service.ts
139
- │ │ └── index.ts
140
- │ │ ├── utils/
141
- │ │ │ ├── utils.module.ts
142
- │ │ │ └── utils.service.ts
143
- │ │ └── index.ts
130
+ │ │ │ └── multi-tenant-datasource.service.ts
131
+ │ │ └── utils/
132
+ │ │ ├── utils.module.ts
133
+ │ │ └── utils.service.ts
144
134
  │ │
145
135
  │ └── utils/ # Utility functions
146
136
  │ ├── error-handler.util.ts
147
- └── index.ts
137
+ ├── query-helpers.util.ts
138
+ │ ├── string.util.ts
139
+ │ ├── request.util.ts
140
+ │ └── html-sanitizer.util.ts
148
141
  ```
149
142
 
150
143
  ---
@@ -159,11 +152,7 @@ The `ApiService` base class provides standardized CRUD operations with caching,
159
152
  import { ApiService, HybridCache } from '@flusys/nestjs-shared/classes';
160
153
  import { UtilsService } from '@flusys/nestjs-shared/modules';
161
154
  import { Injectable, Inject } from '@nestjs/common';
162
- import { InjectRepository } from '@nestjs/typeorm';
163
155
  import { Repository } from 'typeorm';
164
- import { User } from './user.entity';
165
- import { CreateUserDto, UpdateUserDto } from './user.dto';
166
- import { IUser } from './user.interface';
167
156
 
168
157
  @Injectable()
169
158
  export class UserService extends ApiService<
@@ -178,6 +167,7 @@ export class UserService extends ApiService<
178
167
  protected override repository: Repository<User>,
179
168
  @Inject('CACHE_INSTANCE')
180
169
  protected override cacheManager: HybridCache,
170
+ @Inject(UtilsService)
181
171
  protected override utilsService: UtilsService,
182
172
  ) {
183
173
  super(
@@ -189,30 +179,6 @@ export class UserService extends ApiService<
189
179
  true, // Enable caching
190
180
  );
191
181
  }
192
-
193
- // Override to customize DTO to entity conversion
194
- override async convertSingleDtoToEntity(
195
- dto: CreateUserDto | UpdateUserDto,
196
- user: ILoggedUserInfo,
197
- ): Promise<User> {
198
- const entity = new User();
199
- Object.assign(entity, dto);
200
- return entity;
201
- }
202
-
203
- // Override to customize query selection
204
- override async getSelectQuery(
205
- query: SelectQueryBuilder<User>,
206
- user: ILoggedUserInfo,
207
- select?: string[],
208
- ) {
209
- if (!select?.length) {
210
- select = ['id', 'name', 'email', 'createdAt'];
211
- }
212
- const selectFields = select.map(f => `${this.entityName}.${f}`);
213
- query.select(selectFields);
214
- return { query, isRaw: false };
215
- }
216
182
  }
217
183
  ```
218
184
 
@@ -224,11 +190,11 @@ export class UserService extends ApiService<
224
190
  | `insertMany(dtos, user)` | Create multiple entities |
225
191
  | `getById(id, user, select?)` | Get entity by ID |
226
192
  | `findById(id, user, select?)` | Find entity (returns null if not found) |
193
+ | `findByIds(ids, user, select?)` | Find multiple by IDs |
227
194
  | `getAll(dto, user)` | Get paginated list |
228
195
  | `update(dto, user)` | Update single entity |
229
196
  | `updateMany(dtos, user)` | Update multiple entities |
230
- | `delete(dto, user)` | Soft/permanent delete |
231
- | `restore(dto, user)` | Restore soft-deleted |
197
+ | `delete(dto, user)` | Soft/permanent delete or restore |
232
198
 
233
199
  ### Customization Hooks
234
200
 
@@ -244,6 +210,12 @@ export class UserService extends ApiService<...> {
244
210
  // Add WHERE filters
245
211
  override async getFilterQuery(query, filter, user): Promise<{ query, isRaw }> { }
246
212
 
213
+ // Add global search
214
+ override async getGlobalSearchQuery(query, globalSearch, user): Promise<{ query, isRaw }> { }
215
+
216
+ // Add sort order
217
+ override async getSortQuery(query, sort, user): Promise<{ query, isRaw }> { }
218
+
247
219
  // Add extra query conditions (e.g., company filtering)
248
220
  override async getExtraManipulateQuery(query, dto, user): Promise<{ query, isRaw }> { }
249
221
 
@@ -267,32 +239,61 @@ export class UserService extends ApiService<...> {
267
239
  }
268
240
  ```
269
241
 
270
- ### Company/Branch Filtering Example
242
+ ---
243
+
244
+ ## RequestScopedApiService
245
+
246
+ For dynamic entity resolution based on runtime configuration (e.g., company feature).
271
247
 
272
248
  ```typescript
273
- override async getExtraManipulateQuery(
274
- query: SelectQueryBuilder<User>,
275
- dto: FilterAndPaginationDto,
276
- user: ILoggedUserInfo,
277
- ) {
278
- // Filter by user's company
279
- if (user.companyId) {
280
- query.andWhere(`${this.entityName}.companyId = :companyId`, {
281
- companyId: user.companyId,
282
- });
249
+ import { RequestScopedApiService } from '@flusys/nestjs-shared/classes';
250
+ import { Injectable, Scope, Inject } from '@nestjs/common';
251
+ import { EntityTarget, Repository } from 'typeorm';
252
+
253
+ @Injectable({ scope: Scope.REQUEST })
254
+ export class RoleService extends RequestScopedApiService<
255
+ CreateRoleDto,
256
+ UpdateRoleDto,
257
+ IRole,
258
+ RoleBase,
259
+ Repository<RoleBase>
260
+ > {
261
+ constructor(
262
+ @Inject('CACHE_INSTANCE') protected override cacheManager: HybridCache,
263
+ @Inject(UtilsService) protected override utilsService: UtilsService,
264
+ @Inject(ModuleConfigService) private readonly config: ModuleConfigService,
265
+ @Inject(DataSourceProvider) private readonly provider: DataSourceProvider,
266
+ ) {
267
+ // Pass null for repository - will be initialized dynamically
268
+ super('role', null as any, cacheManager, utilsService, 'RoleService', true);
269
+ }
270
+
271
+ // Required: Resolve which entity to use
272
+ protected resolveEntity(): EntityTarget<RoleBase> {
273
+ return this.config.isCompanyFeatureEnabled() ? RoleWithCompany : Role;
283
274
  }
284
275
 
285
- // Filter by user's branch
286
- if (user.branchId) {
287
- query.andWhere(`${this.entityName}.branchId = :branchId`, {
288
- branchId: user.branchId,
289
- });
276
+ // Required: Return the DataSource provider
277
+ protected getDataSourceProvider(): IDataSourceProvider {
278
+ return this.provider;
290
279
  }
291
280
 
292
- return { query, isRaw: false };
281
+ // Optional: Initialize additional repositories
282
+ protected async initializeAdditionalRepositories(): Promise<void> {
283
+ this.actionRepository = await this.dataSourceProvider.getRepository(Action);
284
+ }
293
285
  }
294
286
  ```
295
287
 
288
+ ### Key Methods
289
+
290
+ | Method | Description |
291
+ |--------|-------------|
292
+ | `ensureRepositoryInitialized()` | Must call before using repository |
293
+ | `resolveEntity()` | Abstract - return entity class based on config |
294
+ | `getDataSourceProvider()` | Abstract - return datasource provider |
295
+ | `getDataSourceForService()` | Get raw DataSource for transactions |
296
+
296
297
  ---
297
298
 
298
299
  ## ApiController - Generic CRUD Controller
@@ -303,11 +304,8 @@ The `createApiController` factory creates standardized POST-only RPC controllers
303
304
 
304
305
  ```typescript
305
306
  import { createApiController } from '@flusys/nestjs-shared/classes';
306
- import { Controller } from '@nestjs/common';
307
+ import { Controller, Inject } from '@nestjs/common';
307
308
  import { ApiTags } from '@nestjs/swagger';
308
- import { CreateUserDto, UpdateUserDto, UserResponseDto } from './user.dto';
309
- import { IUser } from './user.interface';
310
- import { UserService } from './user.service';
311
309
 
312
310
  @ApiTags('Users')
313
311
  @Controller('users')
@@ -318,7 +316,7 @@ export class UserController extends createApiController<
318
316
  IUser,
319
317
  UserService
320
318
  >(CreateUserDto, UpdateUserDto, UserResponseDto) {
321
- constructor(protected service: UserService) {
319
+ constructor(@Inject(UserService) protected service: UserService) {
322
320
  super(service);
323
321
  }
324
322
  }
@@ -345,9 +343,7 @@ export class UserController extends createApiController(
345
343
  CreateUserDto,
346
344
  UpdateUserDto,
347
345
  UserResponseDto,
348
- {
349
- security: 'jwt', // All endpoints require JWT
350
- },
346
+ { security: 'jwt' }, // All endpoints require JWT
351
347
  ) {}
352
348
 
353
349
  // Per-endpoint security
@@ -366,29 +362,8 @@ export class UserController extends createApiController(
366
362
  },
367
363
  },
368
364
  ) {}
369
-
370
- // Permission combinations
371
- {
372
- security: {
373
- // Require ANY of these permissions
374
- insert: {
375
- level: 'permission',
376
- permissions: ['users.create', 'users.admin'],
377
- require: 'any', // Default
378
- },
379
-
380
- // Require ALL permissions
381
- delete: {
382
- level: 'permission',
383
- permissions: ['users.delete', 'users.admin'],
384
- require: 'all',
385
- },
386
- },
387
- }
388
365
  ```
389
366
 
390
- See [API-CONTROLLER-SECURITY.md](./API-CONTROLLER-SECURITY.md) for detailed security configuration.
391
-
392
367
  ---
393
368
 
394
369
  ## Decorators
@@ -403,16 +378,22 @@ import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
403
378
 
404
379
  @Controller('profile')
405
380
  export class ProfileController {
406
- @Get()
381
+ @Post('me')
407
382
  getProfile(@CurrentUser() user: ILoggedUserInfo) {
408
383
  return { userId: user.id, companyId: user.companyId };
409
384
  }
385
+
386
+ // Extract specific property
387
+ @Post('id')
388
+ getUserId(@CurrentUser('id') userId: string) {
389
+ return { userId };
390
+ }
410
391
  }
411
392
  ```
412
393
 
413
394
  ### @Public
414
395
 
415
- Mark route as public (skip authentication):
396
+ Mark route as public (skip authentication). **Use sparingly - security risk**.
416
397
 
417
398
  ```typescript
418
399
  import { Public } from '@flusys/nestjs-shared/decorators';
@@ -421,43 +402,44 @@ import { Public } from '@flusys/nestjs-shared/decorators';
421
402
  export class AuthController {
422
403
  @Public()
423
404
  @Post('login')
424
- login() {
425
- // No JWT required
426
- }
405
+ login() { }
427
406
  }
428
407
  ```
429
408
 
430
409
  ### @RequirePermission
431
410
 
432
- Require specific permission:
411
+ Require specific permission(s) - **AND logic** by default:
433
412
 
434
413
  ```typescript
435
414
  import { RequirePermission } from '@flusys/nestjs-shared/decorators';
436
415
 
437
416
  @Controller('admin')
438
417
  export class AdminController {
418
+ // Requires 'admin.dashboard' permission
439
419
  @RequirePermission('admin.dashboard')
440
- @Get('dashboard')
441
- getDashboard() {
442
- // Requires 'admin.dashboard' permission
443
- }
420
+ @Post('dashboard')
421
+ getDashboard() { }
422
+
423
+ // Requires BOTH permissions
424
+ @RequirePermission('users.read', 'admin.access')
425
+ @Post('users')
426
+ getUsers() { }
444
427
  }
445
428
  ```
446
429
 
447
430
  ### @RequireAnyPermission
448
431
 
449
- Require any of the listed permissions:
432
+ Require any of the listed permissions - **OR logic**:
450
433
 
451
434
  ```typescript
452
435
  import { RequireAnyPermission } from '@flusys/nestjs-shared/decorators';
453
436
 
454
437
  @Controller('reports')
455
438
  export class ReportsController {
439
+ // Requires 'reports.view' OR 'reports.admin'
456
440
  @RequireAnyPermission('reports.view', 'reports.admin')
457
- @Get()
458
- getReports() {
459
- // Requires 'reports.view' OR 'reports.admin'
460
- }
441
+ @Post()
442
+ getReports() { }
461
443
  }
462
444
  ```
463
445
 
@@ -470,14 +452,6 @@ import { RequirePermissionCondition } from '@flusys/nestjs-shared/decorators';
470
452
 
471
453
  @Controller('sensitive')
472
454
  export class SensitiveController {
473
- // Simple: User needs 'admin' OR 'manager'
474
- @RequirePermissionCondition({
475
- operator: 'or',
476
- permissions: ['admin', 'manager'],
477
- })
478
- @Get('simple')
479
- getSimpleData() {}
480
-
481
455
  // Complex: User needs 'users.read' AND ('admin' OR 'manager')
482
456
  @RequirePermissionCondition({
483
457
  operator: 'and',
@@ -486,33 +460,52 @@ export class SensitiveController {
486
460
  { operator: 'or', permissions: ['admin', 'manager'] }
487
461
  ]
488
462
  })
489
- @Get('complex')
490
- getComplexData() {}
463
+ @Post('complex')
464
+ getComplexData() { }
465
+ }
466
+ ```
491
467
 
492
- // Very complex: (A AND B) OR (C AND D)
493
- @RequirePermissionCondition({
494
- operator: 'or',
495
- children: [
496
- { operator: 'and', permissions: ['finance.read', 'finance.write'] },
497
- { operator: 'and', permissions: ['admin.full', 'reports.access'] }
498
- ]
499
- })
500
- @Get('very-complex')
501
- getVeryComplexData() {}
468
+ ### @SanitizeHtml / @SanitizeAndTrim
469
+
470
+ Escape HTML entities for XSS prevention:
471
+
472
+ ```typescript
473
+ import { SanitizeHtml, SanitizeAndTrim } from '@flusys/nestjs-shared/decorators';
474
+
475
+ export class CreateCommentDto {
476
+ @SanitizeHtml()
477
+ @IsString()
478
+ content: string;
479
+
480
+ @SanitizeAndTrim() // Escapes HTML AND trims whitespace
481
+ @IsString()
482
+ title: string;
502
483
  }
503
484
  ```
504
485
 
505
- **Note:** For simple "require ALL permissions" use case, use `@RequirePermission` which defaults to AND logic:
486
+ ### @ApiResponseDto
487
+
488
+ Generates Swagger schema for response:
489
+
506
490
  ```typescript
507
- @RequirePermission('admin.access', 'security.clearance') // User needs BOTH
491
+ import { ApiResponseDto } from '@flusys/nestjs-shared/decorators';
492
+
493
+ @Controller('users')
494
+ export class UserController {
495
+ @Post('get-all')
496
+ @ApiResponseDto(UserResponseDto, true, 'list') // Array with PaginationMetaDto
497
+ getAll() { }
498
+
499
+ @Post('insert')
500
+ @ApiResponseDto(UserResponseDto, false) // Single item
501
+ insert() { }
502
+ }
508
503
  ```
509
504
 
510
505
  ---
511
506
 
512
507
  ## Guards
513
508
 
514
- The shared package provides two guards for authentication and authorization:
515
-
516
509
  ### JwtAuthGuard
517
510
 
518
511
  Validates JWT tokens for protected routes. Extends Passport's `AuthGuard('jwt')` and respects `@Public()` decorator.
@@ -520,22 +513,15 @@ Validates JWT tokens for protected routes. Extends Passport's `AuthGuard('jwt')`
520
513
  ```typescript
521
514
  import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
522
515
 
523
- // Apply globally
516
+ // Apply globally in main.ts
524
517
  @Module({
525
518
  providers: [{ provide: APP_GUARD, useClass: JwtAuthGuard }],
526
519
  })
527
520
  export class AppModule {}
528
-
529
- // Or per controller
530
- @Controller('users')
531
- @UseGuards(JwtAuthGuard)
532
- export class UserController {
533
- @Post('login')
534
- @Public() // Skip JWT check
535
- async login() { }
536
- }
537
521
  ```
538
522
 
523
+ **Important:** Constructor needs `@Inject(Reflector)` for bundled code.
524
+
539
525
  ### PermissionGuard
540
526
 
541
527
  Checks user permissions from cache with AND/OR/nested logic support.
@@ -543,9 +529,18 @@ Checks user permissions from cache with AND/OR/nested logic support.
543
529
  ```typescript
544
530
  import { PermissionGuard } from '@flusys/nestjs-shared/guards';
545
531
 
546
- // Apply globally
547
532
  @Module({
548
- providers: [{ provide: APP_GUARD, useClass: PermissionGuard }],
533
+ providers: [
534
+ { provide: APP_GUARD, useClass: PermissionGuard },
535
+ {
536
+ provide: 'PERMISSION_GUARD_CONFIG',
537
+ useValue: {
538
+ enableCompanyFeature: true,
539
+ userPermissionKeyFormat: 'permissions:user:{userId}',
540
+ companyPermissionKeyFormat: 'permissions:company:{companyId}:branch:{branchId}:user:{userId}',
541
+ },
542
+ },
543
+ ],
549
544
  })
550
545
  export class AppModule {}
551
546
  ```
@@ -560,11 +555,15 @@ export class AppModule {}
560
555
  `permissions:company:{companyId}:branch:{branchId}:user:{userId}`
561
556
  ```
562
557
 
563
- **Features:**
564
- - Supports AND/OR operators via `@RequirePermission` and `@RequireAnyPermission`
565
- - Nested conditions via `@RequirePermissionCondition`
566
- - Wildcard support (`*` and `*.suffix`)
567
- - Company/branch scoped permissions
558
+ ### Permission Exceptions
559
+
560
+ ```typescript
561
+ import {
562
+ InsufficientPermissionsException, // 403 - Missing permissions
563
+ NoPermissionsFoundException, // 403 - No permissions in cache
564
+ PermissionSystemUnavailableException, // 500 - Cache unavailable
565
+ } from '@flusys/nestjs-shared/exceptions';
566
+ ```
568
567
 
569
568
  ---
570
569
 
@@ -572,39 +571,24 @@ export class AppModule {}
572
571
 
573
572
  ### LoggerMiddleware
574
573
 
575
- **Location:** `@flusys/nestjs-shared/middlewares/logger.middleware.ts`
576
-
577
- Combined middleware that handles:
578
- 1. **Request Correlation** - Tracks requests across services using AsyncLocalStorage
579
- 2. **HTTP Request/Response Logging** - Structured logging with Winston
580
- 3. **Detailed Request/Response Information** - Complete visibility into all requests
581
-
582
- **Key Features:**
583
- - **Request ID Generation** - Automatic UUID generation or uses `x-request-id` header
584
- - **Tenant ID Tracking** - Extracts `x-tenant-id` from headers for multi-tenant apps
585
- - **AsyncLocalStorage Context** - Thread-safe request context accessible anywhere
586
- - **Security** - Automatically redacts sensitive headers (authorization, cookie, x-api-key)
587
- - **Performance Monitoring** - Logs slow requests (>3s) with dedicated warning logs
588
- - **Body Truncation** - Limits log size to 1000 characters
589
- - **Debug Mode** - Conditionally logs headers and body based on LOG_LEVEL=debug
590
- - **Complete Request Details** - URL, path, query params, content type, user agent, client IP
591
- - **Complete Response Details** - Status code, message, content type, content length, user/company context
592
- - **Multiple Response Hooks** - Captures responses via `res.send()`, `res.json()`, and `res.end()`
593
- - **Error Handling** - Logs response errors with stack traces
594
- - **User Context** - Automatically includes userId and companyId in response logs if available
574
+ Combined middleware for request correlation and HTTP logging.
575
+
576
+ **Features:**
577
+ - Request ID generation/tracking (UUID or from `x-request-id` header)
578
+ - Tenant ID tracking (from `x-tenant-id` header)
579
+ - AsyncLocalStorage context for thread-safe access
580
+ - Automatic sensitive header redaction (authorization, cookie, x-api-key)
581
+ - Performance monitoring (warns on requests > 3s)
582
+ - Body truncation (max 1000 chars)
595
583
 
596
584
  **Usage:**
597
585
 
598
586
  ```typescript
599
587
  import { LoggerMiddleware } from '@flusys/nestjs-shared/middlewares';
600
- import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
601
588
 
602
- @Module({
603
- // ...
604
- })
589
+ @Module({})
605
590
  export class AppModule implements NestModule {
606
591
  configure(consumer: MiddlewareConsumer) {
607
- // Apply to all routes
608
592
  consumer.apply(LoggerMiddleware).forRoutes('*');
609
593
  }
610
594
  }
@@ -625,195 +609,28 @@ import {
625
609
  @Injectable()
626
610
  export class MyService {
627
611
  async doSomething() {
628
- const requestId = getRequestId(); // Get current request ID
629
- const tenantId = getTenantId(); // Get tenant ID from header
630
-
631
- // Set user context after authentication
612
+ const requestId = getRequestId();
613
+ const tenantId = getTenantId();
632
614
  setUserId('user-123');
633
- setCompanyId('company-456');
634
-
635
- // Use in logs
636
- this.logger.info('Processing request', {
637
- requestId,
638
- tenantId,
639
- userId: getUserId(),
640
- companyId: getCompanyId(),
641
- });
642
615
  }
643
616
  }
644
617
  ```
645
618
 
646
- **Request Context Interface:**
647
-
648
- ```typescript
649
- interface IRequestContext {
650
- requestId: string; // UUID or from x-request-id header
651
- tenantId?: string; // From x-tenant-id header
652
- userId?: string; // Set after authentication
653
- companyId?: string; // Set after authentication
654
- startTime: number; // Request start timestamp
655
- }
656
- ```
657
-
658
- **Configuration:**
659
-
660
- ```typescript
661
- // Environment-based configuration
662
- const IS_DEBUG = envConfig.getLogConfig().level === 'debug';
663
- const TENANT_ID_HEADER = 'x-tenant-id';
664
- const EXCLUDED_PATHS = ['/health', '/metrics', '/favicon.ico'];
665
- const EXCLUDED_HEADERS = ['authorization', 'cookie', 'x-api-key'];
666
- const MAX_BODY_LOG_SIZE = 1000;
667
- ```
668
-
669
- **Console Log Format (Development):**
670
-
671
- The development console logger displays HTTP requests in human-readable format:
672
-
673
- ```
674
- 2026-01-17 22:41:23 [INFO ] [HTTP] [POST /auth/login] [200] (45ms) [uuid] Incoming request
675
- 2026-01-17 22:41:23 [INFO ] [HTTP] [POST /auth/login] [200] (45ms) [uuid] (user:user-123) Response [200]
676
- 2026-01-17 22:41:25 [WARN ] [HTTP] [GET /api/users] [404] (12ms) [uuid] Response [404]
677
- 2026-01-17 22:41:30 [ERROR ] [HTTP] [POST /api/orders] [500] (234ms) [uuid] (user:user-123) Response [500]
678
- ```
679
-
680
- **Format Structure:**
681
- - `[timestamp]` - Request timestamp
682
- - `[level]` - Log level (INFO, WARN, ERROR)
683
- - `[context]` - Always "HTTP" for HTTP requests
684
- - `[METHOD /path]` - HTTP method and endpoint path
685
- - `[statusCode]` - HTTP response status (only in response logs)
686
- - `(duration)` - Request duration (only in response logs)
687
- - `[uuid]` - Request correlation ID
688
- - `(user:userId)` - User ID if authenticated (only in response logs)
689
-
690
- **File Log Format (Production):**
691
-
692
- ```json
693
- // Incoming request (Enhanced with full details)
694
- {
695
- "level": "info",
696
- "message": "Incoming request",
697
- "context": "HTTP",
698
- "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
699
- "tenantId": "tenant-123",
700
- "method": "POST",
701
- "url": "/api/users/insert?sort=name",
702
- "path": "/api/users/insert",
703
- "query": { "sort": "name" },
704
- "ip": "192.168.1.100",
705
- "userAgent": "Mozilla/5.0...",
706
- "contentType": "application/json",
707
- "contentLength": "342",
708
- "headers": { ... }, // Only in debug mode
709
- "body": { ... }, // Only in debug mode, truncated to 1000 chars
710
- "params": { ... } // Only in debug mode (route params)
711
- }
712
-
713
- // Response (Enhanced with full details)
714
- {
715
- "level": "info",
716
- "message": "Response [200]",
717
- "context": "HTTP",
718
- "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
719
- "tenantId": "tenant-123",
720
- "method": "POST",
721
- "url": "/api/users/insert?sort=name",
722
- "path": "/api/users/insert",
723
- "statusCode": 200,
724
- "statusMessage": "OK",
725
- "duration": "125ms",
726
- "durationMs": 125,
727
- "contentType": "application/json; charset=utf-8",
728
- "contentLength": "156",
729
- "userId": "user-456", // Automatically added if available
730
- "companyId": "company-789" // Automatically added if available
731
- }
732
-
733
- // Error response (includes response body automatically)
734
- {
735
- "level": "warn",
736
- "message": "Response [400]",
737
- "context": "HTTP",
738
- "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
739
- "tenantId": "tenant-123",
740
- "method": "POST",
741
- "url": "/api/users/insert",
742
- "path": "/api/users/insert",
743
- "statusCode": 400,
744
- "statusMessage": "Bad Request",
745
- "duration": "45ms",
746
- "durationMs": 45,
747
- "userId": "user-456",
748
- "companyId": "company-789",
749
- "responseBody": {
750
- "statusCode": 400,
751
- "message": "Validation failed",
752
- "errors": ["Email is required"]
753
- }
754
- }
755
-
756
- // Slow request warning (Enhanced with more context)
757
- {
758
- "level": "warn",
759
- "message": "Slow request detected",
760
- "context": "HTTP",
761
- "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
762
- "tenantId": "tenant-123",
763
- "userId": "user-456",
764
- "companyId": "company-789",
765
- "method": "POST",
766
- "url": "/api/reports/generate",
767
- "path": "/api/reports/generate",
768
- "duration": "3245ms",
769
- "durationMs": 3245,
770
- "threshold": "3000ms"
771
- }
772
-
773
- // Response error
774
- {
775
- "level": "error",
776
- "message": "Response error",
777
- "context": "HTTP",
778
- "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
779
- "tenantId": "tenant-123",
780
- "method": "POST",
781
- "url": "/api/users/insert",
782
- "path": "/api/users/insert",
783
- "error": "Socket hang up",
784
- "stack": "Error: Socket hang up\n at ..."
785
- }
786
- ```
787
-
788
- **Benefits:**
789
-
790
- - **Request Tracing** - Correlate logs across multiple services using requestId
791
- - **Multi-Tenant Support** - Automatic tenant isolation in logs
792
- - **Security** - Sensitive data automatically redacted
793
- - **Performance Monitoring** - Identify slow endpoints
794
- - **Debugging** - Conditional verbose logging
795
-
796
619
  ---
797
620
 
798
621
  ## Interceptors
799
622
 
800
623
  ### ResponseMetaInterceptor
801
624
 
802
- Adds request metadata to responses:
625
+ Adds `_meta` to all responses:
803
626
 
804
627
  ```typescript
805
- import { ResponseMetaInterceptor } from '@flusys/nestjs-shared/interceptors';
806
-
807
- @UseInterceptors(ResponseMetaInterceptor)
808
- @Controller('users')
809
- export class UserController {}
810
-
811
- // Response:
628
+ // Response includes:
812
629
  {
813
630
  "data": [...],
814
- "meta": {
815
- "timestamp": "2024-01-01T00:00:00.000Z",
631
+ "_meta": {
816
632
  "requestId": "abc-123",
633
+ "timestamp": "2024-01-01T00:00:00.000Z",
817
634
  "responseTime": 45
818
635
  }
819
636
  }
@@ -821,63 +638,23 @@ export class UserController {}
821
638
 
822
639
  ### IdempotencyInterceptor
823
640
 
824
- Prevent duplicate requests:
641
+ Prevents duplicate POST requests using `X-Idempotency-Key` header. Caches responses for 24 hours.
825
642
 
826
- ```typescript
827
- import { IdempotencyInterceptor } from '@flusys/nestjs-shared/interceptors';
643
+ ### SetCreatedByOnBody / SetUpdateByOnBody / SetDeletedByOnBody
828
644
 
829
- @UseInterceptors(IdempotencyInterceptor)
830
- @Controller('payments')
831
- export class PaymentController {
832
- @Post()
833
- // Clients send X-Idempotency-Key header
834
- // Duplicate requests return cached response
835
- processPayment() {}
836
- }
837
- ```
645
+ Auto-set audit user IDs on request body from authenticated user.
838
646
 
839
- ### SetCreatedByOnBody / SetUpdateByOnBody
647
+ ### DeleteEmptyIdFromBodyInterceptor
840
648
 
841
- Auto-set user IDs on request body:
649
+ Removes empty `id` fields from request body (single and array bodies).
842
650
 
843
- ```typescript
844
- import { SetCreatedByOnBody, SetUpdateByOnBody } from '@flusys/nestjs-shared/interceptors';
845
-
846
- @Controller('posts')
847
- export class PostController {
848
- @UseInterceptors(SetCreatedByOnBody)
849
- @Post()
850
- create(@Body() dto: CreatePostDto) {
851
- // dto.createdById is automatically set to current user ID
852
- }
651
+ ### QueryPerformanceInterceptor
853
652
 
854
- @UseInterceptors(SetUpdateByOnBody)
855
- @Put()
856
- update(@Body() dto: UpdatePostDto) {
857
- // dto.updatedById is automatically set to current user ID
858
- }
859
- }
860
- ```
653
+ Monitors execution time and warns if request > 1000ms (configurable).
861
654
 
862
655
  ### Slug Interceptor
863
656
 
864
- Auto-generate slugs from name field using UtilsService (injected via DI):
865
-
866
- ```typescript
867
- import { Slug } from '@flusys/nestjs-shared/interceptors';
868
-
869
- @Controller('products')
870
- export class ProductController {
871
- @UseInterceptors(Slug)
872
- @Post()
873
- create(@Body() dto: CreateProductDto) {
874
- // dto.slug is auto-generated from dto.name
875
- // "My Product" -> "my-product"
876
- }
877
- }
878
- ```
879
-
880
- **Note:** Requires `UtilsModule` to be imported in your module for dependency injection.
657
+ Auto-generates `slug` from `name` field using `UtilsService.transformToSlug()`.
881
658
 
882
659
  ---
883
660
 
@@ -885,29 +662,22 @@ export class ProductController {
885
662
 
886
663
  ### HybridCache
887
664
 
888
- Two-tier caching with in-memory (fast) and Redis (distributed):
665
+ Two-tier caching with in-memory (L1) and Redis (L2):
889
666
 
890
667
  ```typescript
891
668
  import { HybridCache } from '@flusys/nestjs-shared/classes';
892
669
 
893
670
  @Injectable()
894
671
  export class MyService {
895
- constructor(
896
- @Inject('CACHE_INSTANCE')
897
- private cache: HybridCache,
898
- ) {}
672
+ constructor(@Inject('CACHE_INSTANCE') private cache: HybridCache) {}
899
673
 
900
674
  async getData(key: string) {
901
675
  // Check cache first
902
676
  const cached = await this.cache.get(key);
903
677
  if (cached) return cached;
904
678
 
905
- // Fetch from database
906
679
  const data = await this.fetchFromDb();
907
-
908
- // Store in cache (TTL in seconds)
909
- await this.cache.set(key, data, 3600);
910
-
680
+ await this.cache.set(key, data, 3600); // TTL in seconds
911
681
  return data;
912
682
  }
913
683
 
@@ -915,9 +685,9 @@ export class MyService {
915
685
  await this.cache.del(key);
916
686
  }
917
687
 
918
- async invalidatePattern(pattern: string) {
919
- // Delete all keys matching pattern
920
- await this.cache.delByPattern('users:*');
688
+ async invalidateAll() {
689
+ await this.cache.reset(); // Clear L1
690
+ await this.cache.resetL2(); // Clear L2 (Redis)
921
691
  }
922
692
  }
923
693
  ```
@@ -929,40 +699,26 @@ import { CacheModule } from '@flusys/nestjs-shared/modules';
929
699
 
930
700
  @Module({
931
701
  imports: [
932
- CacheModule.forRoot({
933
- // In-memory cache config
934
- memory: {
935
- max: 500, // Max items
936
- ttl: 3600, // Default TTL (seconds)
937
- },
938
-
939
- // Redis config (optional)
940
- redis: {
941
- url: 'redis://localhost:6379',
942
- ttl: 3600,
943
- },
944
- }),
702
+ CacheModule.forRoot(
703
+ true, // isGlobal
704
+ 60_000, // memoryTtl (ms)
705
+ 5000 // memorySize (LRU max items)
706
+ ),
945
707
  ],
946
708
  })
947
709
  export class AppModule {}
948
710
  ```
949
711
 
950
- ### Cache Methods
951
-
952
- | Method | Description |
953
- |--------|-------------|
954
- | `get(key)` | Get cached value |
955
- | `set(key, value, ttl?)` | Set value with optional TTL |
956
- | `del(key)` | Delete single key |
957
- | `delByPattern(pattern)` | Delete keys matching pattern |
958
- | `reset()` | Clear all cache |
959
- | `has(key)` | Check if key exists |
712
+ **Configuration via `USE_CACHE_LABEL` env:**
713
+ - `'memory'` - L1 only
714
+ - `'redis'` - L2 only
715
+ - `'hybrid'` - Both (default)
960
716
 
961
717
  ---
962
718
 
963
719
  ## Multi-Tenant DataSource
964
720
 
965
- Dynamic database connection management with connection pooling and request-scoped tenant resolution.
721
+ Dynamic database connection management with connection pooling.
966
722
 
967
723
  ### Setup
968
724
 
@@ -982,7 +738,7 @@ import { DataSourceModule } from '@flusys/nestjs-shared/modules';
982
738
  },
983
739
  tenants: [
984
740
  { id: 'tenant1', database: 'tenant1_db' },
985
- { id: 'tenant2', database: 'tenant2_db' },
741
+ { id: 'tenant2', database: 'tenant2_db', host: 'other-server.com' },
986
742
  ],
987
743
  }),
988
744
  ],
@@ -990,38 +746,18 @@ import { DataSourceModule } from '@flusys/nestjs-shared/modules';
990
746
  export class AppModule {}
991
747
  ```
992
748
 
993
- ### Key Methods
749
+ ### MultiTenantDataSourceService
994
750
 
995
751
  | Method | Description |
996
752
  |--------|-------------|
997
753
  | `getDataSource()` | Get DataSource for current tenant (from header) |
998
754
  | `getDataSourceForTenant(id)` | Get DataSource for specific tenant |
999
755
  | `getRepository(entity)` | Get repository for current tenant |
1000
- | `withTenant(id, callback)` | Execute callback with specific tenant DataSource |
1001
- | `forAllTenants(callback)` | Execute callback for all active tenants |
756
+ | `withTenant(id, callback)` | Execute callback with specific tenant |
757
+ | `forAllTenants(callback)` | Execute callback for all tenants |
1002
758
  | `registerTenant(config)` | Register new tenant at runtime |
1003
759
  | `removeTenant(id)` | Remove tenant and close connection |
1004
-
1005
- ### Usage
1006
-
1007
- ```typescript
1008
- @Injectable()
1009
- export class TenantService {
1010
- constructor(private dataSource: MultiTenantDataSourceService) {}
1011
-
1012
- async getUsers() {
1013
- // Auto-resolves tenant from X-Tenant-ID header
1014
- const repo = await this.dataSource.getRepository(User);
1015
- return repo.find();
1016
- }
1017
-
1018
- async crossTenantCopy(fromId: string, toId: string) {
1019
- const fromDs = await this.dataSource.getDataSourceForTenant(fromId);
1020
- const toDs = await this.dataSource.getDataSourceForTenant(toId);
1021
- // Copy data between tenants
1022
- }
1023
- }
1024
- ```
760
+ | `getCurrentTenantId()` | Get tenant ID from request header |
1025
761
 
1026
762
  ---
1027
763
 
@@ -1029,89 +765,71 @@ export class TenantService {
1029
765
 
1030
766
  ### FilterAndPaginationDto
1031
767
 
1032
- Standard filtering and pagination:
1033
-
1034
768
  ```typescript
1035
769
  import { FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
1036
770
 
1037
771
  // Request body
1038
772
  {
1039
- "filter": {
1040
- "status": "active",
1041
- "category": "electronics"
1042
- },
1043
- "pagination": {
1044
- "currentPage": 0,
1045
- "pageSize": 10
1046
- },
1047
- "sort": {
1048
- "createdAt": "DESC",
1049
- "name": "ASC"
1050
- },
1051
- "select": ["id", "name", "price"],
773
+ "filter": { "status": "active" },
774
+ "pagination": { "currentPage": 0, "pageSize": 10 },
775
+ "sort": { "createdAt": "DESC" },
776
+ "select": ["id", "name", "email"],
1052
777
  "withDeleted": false
1053
778
  }
1054
779
  ```
1055
780
 
1056
781
  ### DeleteDto
1057
782
 
1058
- Soft or permanent delete:
1059
-
1060
783
  ```typescript
1061
784
  import { DeleteDto } from '@flusys/nestjs-shared/dtos';
1062
785
 
1063
- // Soft delete (sets deletedAt)
1064
- {
1065
- "id": "user-123",
1066
- "type": "soft"
1067
- }
786
+ // Soft delete
787
+ { "id": "uuid", "type": "delete" }
788
+
789
+ // Restore
790
+ { "id": "uuid", "type": "restore" }
1068
791
 
1069
792
  // Permanent delete
1070
- {
1071
- "id": "user-123",
1072
- "type": "permanent"
1073
- }
793
+ { "id": "uuid", "type": "permanent" }
1074
794
 
1075
795
  // Multiple IDs
1076
- {
1077
- "id": ["user-123", "user-456"],
1078
- "type": "soft"
1079
- }
796
+ { "id": ["uuid1", "uuid2"], "type": "delete" }
1080
797
  ```
1081
798
 
1082
- ### ResponsePayloadDto
1083
-
1084
- Standardized response format:
799
+ ### Response DTOs
1085
800
 
1086
801
  ```typescript
1087
- import { ResponsePayloadDto } from '@flusys/nestjs-shared/dtos';
802
+ // Single item
803
+ class SingleResponseDto<T> {
804
+ success: boolean;
805
+ message: string;
806
+ data?: T;
807
+ _meta?: RequestMetaDto;
808
+ }
1088
809
 
1089
- // Success response
1090
- {
1091
- "success": true,
1092
- "data": { ... },
1093
- "message": "Operation successful"
810
+ // Paginated list
811
+ class ListResponseDto<T> {
812
+ success: boolean;
813
+ message: string;
814
+ data?: T[];
815
+ meta: PaginationMetaDto; // { total, page, pageSize, count, hasMore?, totalPages? }
816
+ _meta?: RequestMetaDto;
1094
817
  }
1095
818
 
1096
- // Paginated response
1097
- {
1098
- "success": true,
1099
- "data": [...],
1100
- "pagination": {
1101
- "total": 100,
1102
- "page": 1,
1103
- "limit": 10,
1104
- "totalPages": 10
1105
- }
819
+ // Bulk operations
820
+ class BulkResponseDto<T> {
821
+ success: boolean;
822
+ message: string;
823
+ data?: T[];
824
+ meta: BulkMetaDto; // { count, failed?, total? }
825
+ _meta?: RequestMetaDto;
1106
826
  }
1107
827
 
1108
- // Error response
1109
- {
1110
- "success": false,
1111
- "error": {
1112
- "code": "VALIDATION_ERROR",
1113
- "message": "Invalid input"
1114
- }
828
+ // Message only
829
+ class MessageResponseDto {
830
+ success: boolean;
831
+ message: string;
832
+ _meta?: RequestMetaDto;
1115
833
  }
1116
834
  ```
1117
835
 
@@ -1129,65 +847,174 @@ import { Identity } from '@flusys/nestjs-shared/entities';
1129
847
  @Entity()
1130
848
  export class Product extends Identity {
1131
849
  // Inherited: id, createdAt, updatedAt, deletedAt
850
+ // Inherited: createdById, updatedById, deletedById
1132
851
 
1133
852
  @Column()
1134
853
  name: string;
1135
-
1136
- @Column()
1137
- price: number;
1138
854
  }
1139
855
  ```
1140
856
 
1141
857
  ### UserRoot Entity
1142
858
 
1143
- Base user entity:
859
+ Base user entity with common fields:
1144
860
 
1145
861
  ```typescript
1146
862
  import { UserRoot } from '@flusys/nestjs-shared/entities';
1147
863
 
1148
864
  @Entity()
1149
865
  export class User extends UserRoot {
1150
- // Inherited: id, name, email, password, phone, isActive, createdAt, updatedAt, deletedAt
1151
-
1152
- @Column({ nullable: true })
1153
- customField: string;
866
+ // Inherited: id, name, email, phone, profilePictureId
867
+ // Inherited: isActive, emailVerified, phoneVerified
868
+ // Inherited: lastLoginAt, additionalFields
869
+ // Inherited: createdAt, updatedAt, deletedAt
1154
870
  }
1155
871
  ```
1156
872
 
1157
873
  ---
1158
874
 
1159
- ## Error Handling
875
+ ## Utilities
876
+
877
+ ### Query Helpers
1160
878
 
1161
- ### ErrorHandler
879
+ ```typescript
880
+ import {
881
+ applyCompanyFilter,
882
+ buildCompanyWhereCondition,
883
+ hasCompanyId,
884
+ validateCompanyOwnership,
885
+ } from '@flusys/nestjs-shared/utils';
1162
886
 
1163
- Centralized error handling utility:
887
+ // Add company filter to TypeORM query
888
+ applyCompanyFilter(query, {
889
+ isCompanyFeatureEnabled: true,
890
+ entityAlias: 'entity',
891
+ }, user);
892
+
893
+ // Build where condition for company
894
+ const where = buildCompanyWhereCondition(baseWhere, user, isCompanyFeatureEnabled);
895
+
896
+ // Validate entity belongs to user's company
897
+ validateCompanyOwnership(entity, user, 'Entity');
898
+ ```
899
+
900
+ ### String Utilities
1164
901
 
1165
902
  ```typescript
1166
- import { ErrorHandler } from '@flusys/nestjs-shared/utils';
903
+ import { generateSlug, generateUniqueSlug } from '@flusys/nestjs-shared/utils';
904
+
905
+ // Generate URL-friendly slug
906
+ const slug = generateSlug('My Product Name', 100);
907
+ // Returns: 'my-product-name'
908
+
909
+ // Generate unique slug with collision detection
910
+ const uniqueSlug = await generateUniqueSlug(
911
+ 'My Product',
912
+ async (pattern) => existingSlugs.filter(s => s.startsWith(pattern)),
913
+ 100
914
+ );
915
+ ```
1167
916
 
1168
- @Injectable()
1169
- export class UserService {
1170
- async createUser(dto: CreateUserDto) {
1171
- try {
1172
- return await this.repository.save(dto);
1173
- } catch (error) {
1174
- // Handles TypeORM errors, validation errors, etc.
1175
- throw ErrorHandler.handle(error, 'Failed to create user');
1176
- }
1177
- }
917
+ ### Request Utilities
918
+
919
+ ```typescript
920
+ import {
921
+ isBrowserRequest,
922
+ buildCookieOptions,
923
+ parseDurationToMs,
924
+ } from '@flusys/nestjs-shared/utils';
925
+
926
+ // Detect browser vs API client
927
+ const isBrowser = isBrowserRequest(req);
928
+
929
+ // Build secure cookie options
930
+ const cookieOpts = buildCookieOptions(req);
931
+
932
+ // Parse duration string
933
+ const ms = parseDurationToMs('7d'); // 604800000
934
+ ```
935
+
936
+ ### HTML Sanitizer
937
+
938
+ ```typescript
939
+ import { escapeHtml, escapeHtmlVariables } from '@flusys/nestjs-shared/utils';
940
+
941
+ // Escape single string
942
+ const safe = escapeHtml('<script>alert("xss")</script>');
943
+ // Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
944
+
945
+ // Escape all values in an object
946
+ const safeVars = escapeHtmlVariables({
947
+ userName: '<script>evil</script>',
948
+ message: 'Hello, World!',
949
+ });
950
+ ```
951
+
952
+ ---
953
+
954
+ ## Error Handling
955
+
956
+ ### Error Handler Utilities
957
+
958
+ ```typescript
959
+ import {
960
+ getErrorMessage,
961
+ logError,
962
+ rethrowError,
963
+ logAndRethrow,
964
+ } from '@flusys/nestjs-shared/utils';
965
+
966
+ interface IErrorContext {
967
+ operation?: string;
968
+ entity?: string;
969
+ userId?: string;
970
+ id?: string;
971
+ companyId?: string;
972
+ data?: Record<string, unknown>;
1178
973
  }
974
+
975
+ // Safe error message extraction
976
+ const message = getErrorMessage(error);
977
+
978
+ // Log with sensitive key redaction (password, secret, token, apiKey)
979
+ logError(logger, error, 'createUser', { userId: user.id });
980
+
981
+ // Type-safe rethrow
982
+ rethrowError(error);
983
+
984
+ // Combined log + rethrow
985
+ logAndRethrow(logger, error, 'updateUser', context);
1179
986
  ```
1180
987
 
1181
- ### Error Types
988
+ ---
989
+
990
+ ## Constants
991
+
992
+ ### Permission Constants
1182
993
 
1183
994
  ```typescript
1184
- // Automatically converted to appropriate HTTP exception:
995
+ import { PERMISSIONS } from '@flusys/nestjs-shared/constants';
996
+
997
+ // Organized by module:
998
+ PERMISSIONS.USER.CREATE // 'user.create'
999
+ PERMISSIONS.USER.READ // 'user.read'
1000
+ PERMISSIONS.USER.UPDATE // 'user.update'
1001
+ PERMISSIONS.USER.DELETE // 'user.delete'
1002
+
1003
+ PERMISSIONS.COMPANY.CREATE // 'company.create'
1004
+ PERMISSIONS.BRANCH.CREATE // 'branch.create'
1005
+
1006
+ PERMISSIONS.ROLE.CREATE // 'role.create'
1007
+ PERMISSIONS.FILE.CREATE // 'file.create'
1008
+ PERMISSIONS.FOLDER.CREATE // 'folder.create'
1009
+ // ...etc
1010
+ ```
1185
1011
 
1186
- // TypeORM unique constraint → ConflictException (409)
1187
- // TypeORM foreign key → BadRequestException (400)
1188
- // Validation error → BadRequestException (400)
1189
- // Not found → NotFoundException (404)
1190
- // Unknown → InternalServerErrorException (500)
1012
+ ### Injection Tokens
1013
+
1014
+ ```typescript
1015
+ 'CACHE_INSTANCE' // HybridCache provider
1016
+ 'PERMISSION_GUARD_CONFIG' // PermissionGuard config
1017
+ 'LOGGER_INSTANCE' // Logger provider
1191
1018
  ```
1192
1019
 
1193
1020
  ---
@@ -1200,8 +1027,11 @@ export class UserService {
1200
1027
  // Classes
1201
1028
  import {
1202
1029
  ApiService,
1030
+ RequestScopedApiService,
1203
1031
  createApiController,
1204
1032
  HybridCache,
1033
+ WinstonLoggerAdapter,
1034
+ NestLoggerAdapter,
1205
1035
  } from '@flusys/nestjs-shared/classes';
1206
1036
 
1207
1037
  // Decorators
@@ -1211,13 +1041,13 @@ import {
1211
1041
  RequirePermission,
1212
1042
  RequireAnyPermission,
1213
1043
  RequirePermissionCondition,
1044
+ SanitizeHtml,
1045
+ SanitizeAndTrim,
1046
+ ApiResponseDto,
1214
1047
  } from '@flusys/nestjs-shared/decorators';
1215
1048
 
1216
1049
  // Guards
1217
- import {
1218
- JwtAuthGuard,
1219
- PermissionGuard,
1220
- } from '@flusys/nestjs-shared/guards';
1050
+ import { JwtAuthGuard, PermissionGuard } from '@flusys/nestjs-shared/guards';
1221
1051
 
1222
1052
  // Interceptors
1223
1053
  import {
@@ -1225,6 +1055,9 @@ import {
1225
1055
  IdempotencyInterceptor,
1226
1056
  SetCreatedByOnBody,
1227
1057
  SetUpdateByOnBody,
1058
+ SetDeletedByOnBody,
1059
+ DeleteEmptyIdFromBodyInterceptor,
1060
+ QueryPerformanceInterceptor,
1228
1061
  Slug,
1229
1062
  } from '@flusys/nestjs-shared/interceptors';
1230
1063
 
@@ -1233,31 +1066,76 @@ import {
1233
1066
  CacheModule,
1234
1067
  DataSourceModule,
1235
1068
  UtilsModule,
1069
+ UtilsService,
1070
+ MultiTenantDataSourceService,
1236
1071
  } from '@flusys/nestjs-shared/modules';
1237
1072
 
1238
1073
  // DTOs
1239
1074
  import {
1240
1075
  FilterAndPaginationDto,
1076
+ PaginationDto,
1241
1077
  DeleteDto,
1242
- ResponsePayloadDto,
1078
+ GetByIdBodyDto,
1079
+ SingleResponseDto,
1080
+ ListResponseDto,
1081
+ BulkResponseDto,
1082
+ MessageResponseDto,
1083
+ IdentityResponseDto,
1084
+ PaginationMetaDto,
1085
+ BulkMetaDto,
1086
+ RequestMetaDto,
1243
1087
  } from '@flusys/nestjs-shared/dtos';
1244
1088
 
1245
1089
  // Entities
1246
- import {
1247
- Identity,
1248
- UserRoot,
1249
- } from '@flusys/nestjs-shared/entities';
1090
+ import { Identity, UserRoot } from '@flusys/nestjs-shared/entities';
1250
1091
 
1251
1092
  // Interfaces
1252
1093
  import {
1253
1094
  ILoggedUserInfo,
1254
- IPermissionConfig,
1095
+ IService,
1096
+ IDataSourceProvider,
1097
+ IModuleConfigService,
1098
+ ILogger,
1099
+ PermissionCondition,
1100
+ PermissionOperator,
1255
1101
  } from '@flusys/nestjs-shared/interfaces';
1256
1102
 
1257
1103
  // Utilities
1258
1104
  import {
1259
- ErrorHandler,
1105
+ getErrorMessage,
1106
+ logError,
1107
+ applyCompanyFilter,
1108
+ buildCompanyWhereCondition,
1109
+ validateCompanyOwnership,
1110
+ generateSlug,
1111
+ generateUniqueSlug,
1112
+ isBrowserRequest,
1113
+ buildCookieOptions,
1114
+ parseDurationToMs,
1115
+ escapeHtml,
1116
+ escapeHtmlVariables,
1260
1117
  } from '@flusys/nestjs-shared/utils';
1118
+
1119
+ // Middleware
1120
+ import {
1121
+ LoggerMiddleware,
1122
+ getRequestId,
1123
+ getTenantId,
1124
+ getUserId,
1125
+ getCompanyId,
1126
+ setUserId,
1127
+ setCompanyId,
1128
+ } from '@flusys/nestjs-shared/middlewares';
1129
+
1130
+ // Exceptions
1131
+ import {
1132
+ InsufficientPermissionsException,
1133
+ NoPermissionsFoundException,
1134
+ PermissionSystemUnavailableException,
1135
+ } from '@flusys/nestjs-shared/exceptions';
1136
+
1137
+ // Constants
1138
+ import { PERMISSIONS } from '@flusys/nestjs-shared/constants';
1261
1139
  ```
1262
1140
 
1263
1141
  ---
@@ -1267,101 +1145,58 @@ import {
1267
1145
  ### 1. Use Generic Service Pattern
1268
1146
 
1269
1147
  ```typescript
1270
- // Extend ApiService for consistent CRUD
1148
+ // Extend ApiService for consistent CRUD
1271
1149
  @Injectable()
1272
1150
  export class ProductService extends ApiService<...> {
1273
1151
  // Override hooks for customization
1274
1152
  }
1275
1153
 
1276
- // Don't create custom CRUD from scratch
1277
- @Injectable()
1278
- export class ProductService {
1279
- async getAll() { /* custom implementation */ }
1280
- async getById() { /* custom implementation */ }
1281
- // Inconsistent, error-prone
1282
- }
1154
+ // For dynamic entities, use RequestScopedApiService
1155
+ @Injectable({ scope: Scope.REQUEST })
1156
+ export class RoleService extends RequestScopedApiService<...> { }
1283
1157
  ```
1284
1158
 
1285
- ### 2. Use Decorators Consistently
1159
+ ### 2. Always Use @Inject() Decorators
1160
+
1161
+ Required for esbuild bundled code:
1286
1162
 
1287
1163
  ```typescript
1288
- // ✅ Use built-in decorators
1164
+ // CORRECT
1165
+ constructor(
1166
+ @Inject(MyService) private readonly myService: MyService,
1167
+ @Inject('CACHE_INSTANCE') private readonly cache: HybridCache,
1168
+ ) {}
1169
+
1170
+ // WRONG - fails in bundled code
1171
+ constructor(private readonly myService: MyService) {}
1172
+ ```
1173
+
1174
+ ### 3. Use Decorators Consistently
1175
+
1176
+ ```typescript
1177
+ // Use built-in decorators
1289
1178
  @CurrentUser() user: ILoggedUserInfo
1290
- @Public()
1291
1179
  @RequirePermission('users.create')
1180
+ @Public() // Use sparingly!
1292
1181
 
1293
- // Don't access request directly
1294
- @Req() req: Request
1295
- const user = req.user; // Not type-safe
1182
+ // Don't access request directly
1183
+ @Req() req: Request // Not type-safe
1296
1184
  ```
1297
1185
 
1298
- ### 3. Configure Security at Controller Level
1186
+ ### 4. Configure Security at Controller Level
1299
1187
 
1300
1188
  ```typescript
1301
- // Configure security in createApiController
1189
+ // GOOD - configure in createApiController
1302
1190
  export class UserController extends createApiController(..., {
1303
- security: { ... },
1191
+ security: { insert: 'permission', ... },
1304
1192
  }) {}
1305
1193
 
1306
- // Don't add @UseGuards to each endpoint
1194
+ // AVOID - adding guards to each endpoint
1307
1195
  @UseGuards(JwtGuard)
1308
1196
  @Post('create')
1309
1197
  create() {}
1310
1198
  ```
1311
1199
 
1312
- ### 4. Use Cache for Repeated Queries
1313
-
1314
- ```typescript
1315
- // ✅ Cache expensive operations
1316
- async getPermissions(userId: string) {
1317
- const cacheKey = `permissions:${userId}`;
1318
- const cached = await this.cache.get(cacheKey);
1319
- if (cached) return cached;
1320
-
1321
- const permissions = await this.fetchPermissions(userId);
1322
- await this.cache.set(cacheKey, permissions, 3600);
1323
- return permissions;
1324
- }
1325
- ```
1326
-
1327
- ---
1328
-
1329
- ## Dependencies
1330
-
1331
- - `@nestjs/common`
1332
- - `@nestjs/core`
1333
- - `@nestjs/typeorm`
1334
- - `@nestjs/swagger`
1335
- - `typeorm`
1336
- - `class-validator`
1337
- - `class-transformer`
1338
- - `@flusys/nestjs-core`
1339
-
1340
- ---
1341
-
1342
- ## Related Documentation
1343
-
1344
- - [API Controller Security Guide](./API-CONTROLLER-SECURITY.md)
1345
- - [Core Package Guide](./CORE-GUIDE.md)
1346
- - [Auth Package Guide](./AUTH-GUIDE.md)
1347
-
1348
- ---
1349
-
1350
- ## Summary
1351
-
1352
- The `@flusys/nestjs-shared` package provides:
1353
-
1354
- - **Generic CRUD** - Consistent API patterns
1355
- - **Permission System** - Flexible access control
1356
- - **Caching** - High-performance data access
1357
- - **Multi-Tenancy** - Database isolation
1358
- - **Request Correlation** - AsyncLocalStorage-based tracking
1359
- - **Middleware** - Logging and performance monitoring
1360
- - **Interceptors** - Request/response processing
1361
- - **Error Handling** - Centralized error management
1362
-
1363
- This is the shared infrastructure layer used by all other Flusys packages.
1364
-
1365
1200
  ---
1366
1201
 
1367
- **Last Updated:** 2026-02-18
1202
+ **Last Updated:** 2026-02-21