@flusys/nestjs-shared 1.1.0 → 1.2.0-rc.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 (158) hide show
  1. package/README.md +139 -1103
  2. package/cjs/classes/api-controller.class.js +320 -29
  3. package/cjs/classes/api-service.class.js +219 -64
  4. package/cjs/classes/handle-custome-db.js +37 -0
  5. package/cjs/classes/index.js +1 -2
  6. package/cjs/classes/winston.logger.class.js +103 -70
  7. package/cjs/constants/index.js +1 -4
  8. package/cjs/constants/message-keys.js +68 -0
  9. package/cjs/constants/permissions.js +114 -11
  10. package/cjs/decorators/api-response.decorator.js +1 -2
  11. package/cjs/decorators/index.js +1 -1
  12. package/cjs/decorators/log-action.decorator.js +149 -0
  13. package/cjs/decorators/require-permission.decorator.js +5 -5
  14. package/cjs/dtos/get-by-ids.dto.js +65 -0
  15. package/cjs/dtos/identity-response.dto.js +4 -8
  16. package/cjs/dtos/index.js +1 -0
  17. package/cjs/dtos/response-payload.dto.js +72 -0
  18. package/cjs/entities/identity.js +8 -2
  19. package/cjs/entities/index.js +1 -0
  20. package/cjs/entities/raw-type.js +4 -0
  21. package/cjs/entities/user-root.js +2 -1
  22. package/cjs/enums/index.js +20 -0
  23. package/cjs/enums/notification-type.enum.js +17 -0
  24. package/cjs/enums/participant-status.enum.js +17 -0
  25. package/cjs/enums/recurrence-type.enum.js +18 -0
  26. package/cjs/exceptions/base-app.exception.js +151 -0
  27. package/cjs/exceptions/index.js +1 -0
  28. package/cjs/exceptions/permission.exception.js +13 -9
  29. package/cjs/filters/global-exception.filter.js +279 -0
  30. package/cjs/filters/index.js +18 -0
  31. package/cjs/guards/jwt-auth.guard.js +4 -1
  32. package/cjs/guards/permission.guard.js +71 -77
  33. package/cjs/index.js +2 -0
  34. package/cjs/interceptors/idempotency.interceptor.js +1 -1
  35. package/cjs/interceptors/index.js +0 -1
  36. package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
  37. package/cjs/interfaces/index.js +2 -1
  38. package/cjs/interfaces/notification-adapter.interface.js +11 -0
  39. package/cjs/interfaces/permission.interface.js +1 -1
  40. package/cjs/middlewares/logger.middleware.js +83 -26
  41. package/cjs/modules/datasource/index.js +0 -1
  42. package/cjs/modules/datasource/multi-tenant-datasource.service.js +46 -85
  43. package/cjs/modules/utils/utils.service.js +4 -20
  44. package/cjs/utils/date-time.util.js +94 -0
  45. package/cjs/utils/index.js +1 -1
  46. package/cjs/utils/query-helpers.util.js +8 -1
  47. package/cjs/utils/request.util.js +25 -0
  48. package/classes/api-controller.class.d.ts +14 -6
  49. package/classes/api-service.class.d.ts +25 -22
  50. package/classes/handle-custome-db.d.ts +3 -0
  51. package/classes/index.d.ts +1 -2
  52. package/classes/winston.logger.class.d.ts +1 -0
  53. package/constants/index.d.ts +1 -1
  54. package/constants/message-keys.d.ts +45 -0
  55. package/constants/permissions.d.ts +127 -0
  56. package/decorators/api-response.decorator.d.ts +1 -1
  57. package/decorators/current-user.decorator.d.ts +1 -1
  58. package/decorators/index.d.ts +1 -1
  59. package/decorators/log-action.decorator.d.ts +8 -0
  60. package/decorators/require-permission.decorator.d.ts +2 -2
  61. package/dtos/get-by-ids.dto.d.ts +4 -0
  62. package/dtos/index.d.ts +1 -0
  63. package/dtos/response-payload.dto.d.ts +8 -0
  64. package/entities/index.d.ts +1 -0
  65. package/entities/raw-type.d.ts +1 -0
  66. package/enums/index.d.ts +3 -0
  67. package/enums/notification-type.enum.d.ts +6 -0
  68. package/enums/participant-status.enum.d.ts +6 -0
  69. package/enums/recurrence-type.enum.d.ts +7 -0
  70. package/exceptions/base-app.exception.d.ts +41 -0
  71. package/exceptions/index.d.ts +1 -0
  72. package/exceptions/permission.exception.d.ts +2 -2
  73. package/fesm/classes/api-controller.class.js +321 -30
  74. package/fesm/classes/api-service.class.js +222 -67
  75. package/fesm/classes/handle-custome-db.js +19 -0
  76. package/fesm/classes/index.js +1 -2
  77. package/fesm/classes/winston.logger.class.js +100 -68
  78. package/fesm/constants/index.js +2 -1
  79. package/fesm/constants/message-keys.js +49 -0
  80. package/fesm/constants/permissions.js +86 -14
  81. package/fesm/decorators/api-response.decorator.js +2 -20
  82. package/fesm/decorators/index.js +1 -1
  83. package/fesm/decorators/log-action.decorator.js +139 -0
  84. package/fesm/decorators/require-permission.decorator.js +17 -65
  85. package/fesm/dtos/get-by-ids.dto.js +55 -0
  86. package/fesm/dtos/identity-response.dto.js +4 -8
  87. package/fesm/dtos/index.js +1 -0
  88. package/fesm/dtos/response-payload.dto.js +72 -0
  89. package/fesm/entities/identity.js +8 -2
  90. package/fesm/entities/index.js +1 -0
  91. package/fesm/entities/raw-type.js +1 -0
  92. package/fesm/entities/user-root.js +2 -1
  93. package/fesm/enums/index.js +3 -0
  94. package/fesm/enums/notification-type.enum.js +7 -0
  95. package/fesm/enums/participant-status.enum.js +7 -0
  96. package/fesm/enums/recurrence-type.enum.js +8 -0
  97. package/fesm/exceptions/base-app.exception.js +115 -0
  98. package/fesm/exceptions/index.js +1 -0
  99. package/fesm/exceptions/permission.exception.js +16 -18
  100. package/fesm/filters/global-exception.filter.js +269 -0
  101. package/fesm/filters/index.js +1 -0
  102. package/fesm/guards/jwt-auth.guard.js +5 -2
  103. package/fesm/guards/permission.guard.js +73 -79
  104. package/fesm/index.js +2 -0
  105. package/fesm/interceptors/idempotency.interceptor.js +2 -2
  106. package/fesm/interceptors/index.js +0 -1
  107. package/fesm/interfaces/datasource.interface.js +1 -20
  108. package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
  109. package/fesm/interfaces/index.js +2 -1
  110. package/fesm/interfaces/notification-adapter.interface.js +1 -0
  111. package/fesm/interfaces/permission.interface.js +1 -3
  112. package/fesm/middlewares/logger.middleware.js +83 -26
  113. package/fesm/modules/datasource/index.js +0 -1
  114. package/fesm/modules/datasource/multi-tenant-datasource.service.js +47 -86
  115. package/fesm/modules/utils/utils.service.js +5 -21
  116. package/fesm/utils/date-time.util.js +70 -0
  117. package/fesm/utils/index.js +1 -1
  118. package/fesm/utils/query-helpers.util.js +8 -1
  119. package/fesm/utils/request.util.js +22 -0
  120. package/filters/global-exception.filter.d.ts +9 -0
  121. package/filters/index.d.ts +1 -0
  122. package/guards/permission.guard.d.ts +3 -7
  123. package/index.d.ts +2 -0
  124. package/interceptors/idempotency.interceptor.d.ts +1 -1
  125. package/interceptors/index.d.ts +0 -1
  126. package/interfaces/api.interface.d.ts +2 -0
  127. package/interfaces/event-manager-adapter.interface.d.ts +43 -0
  128. package/interfaces/index.d.ts +2 -1
  129. package/interfaces/notification-adapter.interface.d.ts +21 -0
  130. package/interfaces/permission.interface.d.ts +13 -8
  131. package/modules/datasource/index.d.ts +0 -1
  132. package/modules/datasource/multi-tenant-datasource.service.d.ts +5 -15
  133. package/modules/utils/utils.service.d.ts +0 -1
  134. package/package.json +21 -14
  135. package/utils/date-time.util.d.ts +8 -0
  136. package/utils/index.d.ts +1 -1
  137. package/utils/request.util.d.ts +2 -0
  138. package/cjs/classes/request-scoped-api.service.js +0 -61
  139. package/cjs/classes/winston-logger-adapter.class.js +0 -104
  140. package/cjs/decorators/sanitize-html.decorator.js +0 -36
  141. package/cjs/interceptors/query-performance.interceptor.js +0 -66
  142. package/cjs/interfaces/logger.interface.js +0 -7
  143. package/cjs/modules/datasource/datasource.module.js +0 -117
  144. package/cjs/utils/error-handler.util.js +0 -90
  145. package/classes/request-scoped-api.service.d.ts +0 -14
  146. package/classes/winston-logger-adapter.class.d.ts +0 -22
  147. package/decorators/sanitize-html.decorator.d.ts +0 -2
  148. package/fesm/classes/request-scoped-api.service.js +0 -51
  149. package/fesm/classes/winston-logger-adapter.class.js +0 -107
  150. package/fesm/decorators/sanitize-html.decorator.js +0 -45
  151. package/fesm/interceptors/query-performance.interceptor.js +0 -56
  152. package/fesm/interfaces/logger.interface.js +0 -4
  153. package/fesm/modules/datasource/datasource.module.js +0 -107
  154. package/fesm/utils/error-handler.util.js +0 -82
  155. package/interceptors/query-performance.interceptor.d.ts +0 -8
  156. package/interfaces/logger.interface.d.ts +0 -7
  157. package/modules/datasource/datasource.module.d.ts +0 -19
  158. package/utils/error-handler.util.d.ts +0 -19
package/README.md CHANGED
@@ -1,667 +1,210 @@
1
- # Shared Package Guide
1
+ # @flusys/nestjs-shared
2
2
 
3
- > **Package:** `@flusys/nestjs-shared`
4
- > **Version:** 1.1.0
5
- > **Type:** Shared NestJS utilities, classes, decorators, guards, and modules
3
+ Shared infrastructure for all FLUSYS NestJS packages — base CRUD classes, JWT guard, permission guard, response DTOs, hybrid cache, structured logging, and soft-delete entities.
6
4
 
7
- This comprehensive guide covers the shared package - the shared NestJS infrastructure layer.
8
-
9
- ## Table of Contents
10
-
11
- - [Overview](#overview)
12
- - [Package Architecture](#package-architecture)
13
- - [ApiService - Generic CRUD Service](#apiservice---generic-crud-service)
14
- - [RequestScopedApiService](#requestscopedapiservice)
15
- - [ApiController - Generic CRUD Controller](#apicontroller---generic-crud-controller)
16
- - [Decorators](#decorators)
17
- - [Guards](#guards)
18
- - [Middleware](#middleware)
19
- - [Interceptors](#interceptors)
20
- - [Caching System](#caching-system)
21
- - [Multi-Tenant DataSource](#multi-tenant-datasource)
22
- - [DTOs](#dtos)
23
- - [Base Entities](#base-entities)
24
- - [Utilities](#utilities)
25
- - [Error Handling](#error-handling)
26
- - [Constants](#constants)
27
- - [API Reference](#api-reference)
5
+ [![npm version](https://img.shields.io/npm/v/@flusys/nestjs-shared.svg)](https://www.npmjs.com/package/@flusys/nestjs-shared)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
28
7
 
29
8
  ---
30
9
 
31
- ## Overview
32
-
33
- `@flusys/nestjs-shared` provides shared utilities for building scalable NestJS applications:
34
-
35
- - **Generic CRUD** - Standardized API controller and service patterns
36
- - **Permission System** - Role and permission-based access control with complex logic
37
- - **Caching** - In-memory + Redis hybrid caching (HybridCache)
38
- - **Request Correlation** - AsyncLocalStorage-based request tracking
39
- - **Middleware** - Logging, correlation, and performance monitoring
40
- - **Interceptors** - Response metadata, idempotency, auto field setting
41
- - **Multi-Tenancy** - Dynamic database connection management
42
- - **Error Handling** - Centralized error handling with sensitive data redaction
10
+ ## Installation
43
11
 
44
- ### Package Hierarchy
45
-
46
- ```
47
- @flusys/nestjs-core <- Pure TypeScript (foundation)
48
- |
49
- @flusys/nestjs-shared <- Shared NestJS utilities (THIS PACKAGE)
50
- |
51
- @flusys/nestjs-auth <- Uses common classes
52
- |
53
- @flusys/nestjs-iam <- Uses common patterns
54
- |
55
- @flusys/nestjs-storage <- Uses common patterns
12
+ ```bash
13
+ npm install @flusys/nestjs-shared @flusys/nestjs-core
14
+ npm install typeorm @nestjs/passport @nestjs/jwt passport-jwt winston
56
15
  ```
57
16
 
58
- ---
59
-
60
- ## Package Architecture
61
-
62
- ```
63
- nestjs-shared/
64
- ├── src/
65
- │ ├── classes/ # Base classes
66
- │ │ ├── api-service.class.ts # Generic CRUD service
67
- │ │ ├── api-controller.class.ts # Generic CRUD controller factory (createApiController)
68
- │ │ ├── request-scoped-api.service.ts # REQUEST-scoped service base
69
- │ │ ├── hybrid-cache.class.ts # Two-tier caching
70
- │ │ ├── winston.logger.class.ts # Winston logger config
71
- │ │ └── winston-logger-adapter.class.ts
72
- │ │
73
- │ ├── constants/ # Injection tokens & constants
74
- │ │ ├── permissions.ts # Permission constants (PERMISSIONS)
75
- │ │ └── index.ts
76
- │ │
77
- │ ├── decorators/ # Custom decorators
78
- │ │ ├── api-response.decorator.ts # @ApiResponseDto
79
- │ │ ├── current-user.decorator.ts # @CurrentUser
80
- │ │ ├── public.decorator.ts # @Public
81
- │ │ ├── require-permission.decorator.ts # @RequirePermission, @RequireAnyPermission, @RequirePermissionCondition
82
- │ │ ├── sanitize-html.decorator.ts # @SanitizeHtml, @SanitizeAndTrim
83
- │ │ └── index.ts
84
- │ │
85
- │ ├── dtos/ # Shared DTOs
86
- │ │ ├── delete.dto.ts
87
- │ │ ├── filter-and-pagination.dto.ts
88
- │ │ ├── identity-response.dto.ts
89
- │ │ ├── pagination.dto.ts
90
- │ │ ├── response-payload.dto.ts
91
- │ │ └── index.ts
92
- │ │
93
- │ ├── entities/ # Base entities
94
- │ │ ├── identity.ts # Base entity with UUID
95
- │ │ ├── user-root.ts # Base user entity
96
- │ │ └── index.ts
97
- │ │
98
- │ ├── exceptions/ # Custom exceptions
99
- │ │ └── permission.exception.ts # Permission-related exceptions
100
- │ │
101
- │ ├── guards/ # Authentication & authorization
102
- │ │ ├── jwt-auth.guard.ts # JWT token validation
103
- │ │ └── permission.guard.ts # Permission checks
104
- │ │
105
- │ ├── interceptors/ # Request/response interceptors
106
- │ │ ├── delete-empty-id-from-body.interceptor.ts
107
- │ │ ├── idempotency.interceptor.ts
108
- │ │ ├── query-performance.interceptor.ts
109
- │ │ ├── response-meta.interceptor.ts
110
- │ │ ├── set-user-field-on-body.interceptor.ts # SetCreatedByOnBody, SetUpdateByOnBody, SetDeletedByOnBody
111
- │ │ └── slug.interceptor.ts
112
- │ │
113
- │ ├── interfaces/ # TypeScript interfaces
114
- │ │ ├── api.interface.ts # IService interface
115
- │ │ ├── datasource.interface.ts # IDataSourceProvider
116
- │ │ ├── identity.interface.ts
117
- │ │ ├── logged-user-info.interface.ts # ILoggedUserInfo
118
- │ │ ├── logger.interface.ts # ILogger
119
- │ │ ├── module-config.interface.ts # IModuleConfigService
120
- │ │ └── permission.interface.ts # PermissionCondition, PermissionOperator
121
- │ │
122
- │ ├── middlewares/ # Middleware
123
- │ │ └── logger.middleware.ts # Request logging & correlation
124
- │ │
125
- │ ├── modules/ # NestJS modules
126
- │ │ ├── cache/cache.module.ts
127
- │ │ ├── datasource/
128
- │ │ │ ├── datasource.module.ts
129
- │ │ │ └── multi-tenant-datasource.service.ts
130
- │ │ └── utils/
131
- │ │ ├── utils.module.ts
132
- │ │ └── utils.service.ts
133
- │ │
134
- │ └── utils/ # Utility functions
135
- │ ├── error-handler.util.ts
136
- │ ├── query-helpers.util.ts
137
- │ ├── string.util.ts
138
- │ ├── request.util.ts
139
- │ └── html-sanitizer.util.ts
140
- ```
141
-
142
- ---
17
+ ## 1. Module Registration
143
18
 
144
- ## ApiService - Generic CRUD Service
145
-
146
- The `ApiService` base class provides standardized CRUD operations with caching, pagination, and transaction support.
147
-
148
- ### Basic Usage
19
+ Register once in your root `AppModule`:
149
20
 
150
21
  ```typescript
151
- import { ApiService, HybridCache } from '@flusys/nestjs-shared/classes';
152
- import { UtilsService } from '@flusys/nestjs-shared/modules';
153
- import { Injectable, Inject } from '@nestjs/common';
154
- import { Repository } from 'typeorm';
22
+ import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
23
+ import { CacheModule, UtilsModule } from '@flusys/nestjs-shared/modules';
24
+ import { LoggerMiddleware } from '@flusys/nestjs-shared/middlewares';
25
+ import { GlobalExceptionFilter } from '@flusys/nestjs-shared/filters';
26
+ import { ResponseMetaInterceptor } from '@flusys/nestjs-shared/interceptors';
155
27
 
156
- @Injectable()
157
- export class UserService extends ApiService<
158
- CreateUserDto, // Create DTO
159
- UpdateUserDto, // Update DTO
160
- IUser, // Interface
161
- User, // Entity
162
- Repository<User> // Repository type
163
- > {
164
- constructor(
165
- @InjectRepository(User)
166
- protected override repository: Repository<User>,
167
- @Inject('CACHE_INSTANCE')
168
- protected override cacheManager: HybridCache,
169
- @Inject(UtilsService)
170
- protected override utilsService: UtilsService,
171
- ) {
172
- super(
173
- 'user', // Entity name (for query building)
174
- repository,
175
- cacheManager,
176
- utilsService,
177
- 'UserService', // Service name (for logging)
178
- true, // Enable caching
179
- );
28
+ @Module({
29
+ imports: [
30
+ CacheModule.forRoot(true), // true = global; provides 'CACHE_INSTANCE' token
31
+ UtilsModule, // provides UtilsService
32
+ ],
33
+ })
34
+ export class AppModule implements NestModule {
35
+ configure(consumer: MiddlewareConsumer) {
36
+ consumer.apply(LoggerMiddleware).forRoutes('*');
180
37
  }
181
38
  }
182
- ```
183
39
 
184
- ### ApiService Methods
40
+ // In bootstrap()
41
+ app.useGlobalFilters(new GlobalExceptionFilter());
42
+ app.useGlobalInterceptors(new ResponseMetaInterceptor());
43
+ ```
185
44
 
186
- | Method | Description |
187
- |--------|-------------|
188
- | `insert(dto, user)` | Create single entity |
189
- | `insertMany(dtos, user)` | Create multiple entities |
190
- | `getById(id, user, select?)` | Get entity by ID |
191
- | `findById(id, user, select?)` | Find entity (returns null if not found) |
192
- | `findByIds(ids, user, select?)` | Find multiple by IDs |
193
- | `getAll(dto, user)` | Get paginated list |
194
- | `update(dto, user)` | Update single entity |
195
- | `updateMany(dtos, user)` | Update multiple entities |
196
- | `delete(dto, user)` | Soft/permanent delete or restore |
45
+ ## 2. Entity Base Class
197
46
 
198
- ### Customization Hooks
47
+ Every entity must extend `Identity`:
199
48
 
200
49
  ```typescript
201
- @Injectable()
202
- export class UserService extends ApiService<...> {
203
- // Convert DTO to entity
204
- override async convertSingleDtoToEntity(dto, user): Promise<User> { }
205
-
206
- // Customize SELECT query
207
- override async getSelectQuery(query, user, select?): Promise<{ query, isRaw }> { }
208
-
209
- // Add WHERE filters
210
- override async getFilterQuery(query, filter, user): Promise<{ query, isRaw }> { }
211
-
212
- // Add global search
213
- override async getGlobalSearchQuery(query, globalSearch, user): Promise<{ query, isRaw }> { }
214
-
215
- // Add sort order
216
- override async getSortQuery(query, sort, user): Promise<{ query, isRaw }> { }
217
-
218
- // Add extra query conditions (e.g., company filtering)
219
- override async getExtraManipulateQuery(query, dto, user): Promise<{ query, isRaw }> { }
220
-
221
- // Before insert hook
222
- override async beforeInsertOperation(entity, user, queryRunner): Promise<void> { }
223
-
224
- // After insert hook
225
- override async afterInsertOperation(entity, user, queryRunner): Promise<void> { }
226
-
227
- // Before update hook
228
- override async beforeUpdateOperation(oldEntity, newEntity, user, queryRunner): Promise<void> { }
229
-
230
- // After update hook
231
- override async afterUpdateOperation(entity, user, queryRunner): Promise<void> { }
232
-
233
- // Before delete hook
234
- override async beforeDeleteOperation(dto, user, queryRunner): Promise<void> { }
50
+ import { Entity, Column } from 'typeorm';
51
+ import { Identity } from '@flusys/nestjs-shared/entities';
235
52
 
236
- // After delete hook
237
- override async afterDeleteOperation(dto, user, queryRunner): Promise<void> { }
53
+ @Entity('products')
54
+ export class Product extends Identity {
55
+ @Column({ length: 255 })
56
+ name: string;
57
+ // inherited: id (uuid), createdAt, updatedAt, deletedAt,
58
+ // createdById, updatedById, deletedById
238
59
  }
239
60
  ```
240
61
 
241
- ---
242
-
243
- ## RequestScopedApiService
62
+ ## 3. Generic CRUD — `createApiController` + `RequestScopedApiService`
244
63
 
245
- For dynamic entity resolution based on runtime configuration (e.g., company feature).
64
+ ### Service (REQUEST-scoped, DataSource Provider pattern)
246
65
 
247
66
  ```typescript
248
- import { RequestScopedApiService } from '@flusys/nestjs-shared/classes';
249
67
  import { Injectable, Scope, Inject } from '@nestjs/common';
250
68
  import { EntityTarget, Repository } from 'typeorm';
69
+ import { RequestScopedApiService } from '@flusys/nestjs-shared/classes';
70
+ import { HybridCache } from '@flusys/nestjs-shared/classes';
71
+ import { UtilsService } from '@flusys/nestjs-shared/modules';
251
72
 
252
- @Injectable({ scope: Scope.REQUEST })
253
- export class RoleService extends RequestScopedApiService<
254
- CreateRoleDto,
255
- UpdateRoleDto,
256
- IRole,
257
- RoleBase,
258
- Repository<RoleBase>
73
+ @Injectable({ scope: Scope.REQUEST }) // required
74
+ export class ProductService extends RequestScopedApiService<
75
+ CreateProductDto,
76
+ UpdateProductDto,
77
+ IProduct,
78
+ Product
259
79
  > {
260
80
  constructor(
261
81
  @Inject('CACHE_INSTANCE') protected override cacheManager: HybridCache,
262
82
  @Inject(UtilsService) protected override utilsService: UtilsService,
263
- @Inject(ModuleConfigService) private readonly config: ModuleConfigService,
264
- @Inject(DataSourceProvider) private readonly provider: DataSourceProvider,
83
+ @Inject(ProductConfigService) private readonly config: ProductConfigService,
84
+ @Inject(ProductDataSourceProvider) private readonly dsp: ProductDataSourceProvider,
265
85
  ) {
266
- // Pass null for repository - will be initialized dynamically
267
- super('role', null as any, cacheManager, utilsService, 'RoleService', true);
86
+ super('product', null as any, cacheManager, utilsService, ProductService.name, true);
268
87
  }
269
88
 
270
- // Required: Resolve which entity to use
271
- protected resolveEntity(): EntityTarget<RoleBase> {
272
- return this.config.isCompanyFeatureEnabled() ? RoleWithCompany : Role;
89
+ // Required: tell the base class which entity and which datasource to use
90
+ protected resolveEntity(): EntityTarget<Product> {
91
+ return this.config.isCompanyFeatureEnabled() ? ProductWithCompany : Product;
273
92
  }
274
93
 
275
- // Required: Return the DataSource provider
276
- protected getDataSourceProvider(): IDataSourceProvider {
277
- return this.provider;
278
- }
279
-
280
- // Optional: Initialize additional repositories
281
- protected async initializeAdditionalRepositories(): Promise<void> {
282
- this.actionRepository = await this.dataSourceProvider.getRepository(Action);
94
+ protected getDataSourceProvider() {
95
+ return this.dsp;
283
96
  }
284
97
  }
285
98
  ```
286
99
 
287
- ### Key Methods
288
-
289
- | Method | Description |
290
- |--------|-------------|
291
- | `ensureRepositoryInitialized()` | Must call before using repository |
292
- | `resolveEntity()` | Abstract - return entity class based on config |
293
- | `getDataSourceProvider()` | Abstract - return datasource provider |
294
- | `getDataSourceForService()` | Get raw DataSource for transactions |
295
-
296
- ---
297
-
298
- ## ApiController - Generic CRUD Controller
299
-
300
- The `createApiController` factory creates standardized POST-only RPC controllers.
301
-
302
- ### Basic Usage
100
+ ### Controller (factory function)
303
101
 
304
102
  ```typescript
305
- import { createApiController } from '@flusys/nestjs-shared/classes';
306
103
  import { Controller, Inject } from '@nestjs/common';
307
- import { ApiTags } from '@nestjs/swagger';
308
-
309
- @ApiTags('Users')
310
- @Controller('users')
311
- export class UserController extends createApiController<
312
- CreateUserDto,
313
- UpdateUserDto,
314
- UserResponseDto,
315
- IUser,
316
- UserService
317
- >(CreateUserDto, UpdateUserDto, UserResponseDto) {
318
- constructor(@Inject(UserService) protected service: UserService) {
319
- super(service);
320
- }
321
- }
322
- ```
323
-
324
- ### Generated Endpoints
325
-
326
- | Endpoint | Method | Description |
327
- |----------|--------|-------------|
328
- | `/insert` | POST | Create entity |
329
- | `/insert-many` | POST | Create multiple entities |
330
- | `/get/:id` | POST | Get entity by ID |
331
- | `/get-all` | POST | Get paginated list |
332
- | `/update` | POST | Update entity |
333
- | `/update-many` | POST | Update multiple entities |
334
- | `/delete` | POST | Delete entity |
335
-
336
- ### Security Configuration
337
-
338
- ```typescript
339
- // Global security (all endpoints)
340
- @Controller('users')
341
- export class UserController extends createApiController(
342
- CreateUserDto,
343
- UpdateUserDto,
344
- UserResponseDto,
345
- { security: 'jwt' }, // All endpoints require JWT
346
- ) {}
104
+ import { createApiController } from '@flusys/nestjs-shared/classes';
347
105
 
348
- // Per-endpoint security
349
- @Controller('users')
350
- export class UserController extends createApiController(
351
- CreateUserDto,
352
- UpdateUserDto,
353
- UserResponseDto,
354
- {
355
- security: {
356
- getAll: 'public', // No auth required
357
- getById: 'jwt', // JWT required
358
- insert: { level: 'permission', permissions: ['users.create'] },
359
- update: { level: 'permission', permissions: ['users.update'] },
360
- delete: { level: 'permission', permissions: ['users.delete'] },
361
- },
106
+ @Controller('administration/products')
107
+ export class ProductController extends createApiController<
108
+ CreateProductDto,
109
+ UpdateProductDto,
110
+ ProductResponseDto,
111
+ IProduct,
112
+ ProductService
113
+ >(CreateProductDto, UpdateProductDto, ProductResponseDto, {
114
+ entityName: 'product',
115
+ security: {
116
+ insert: { level: 'permission', permissions: ['product.create'] },
117
+ insertMany: { level: 'permission', permissions: ['product.create'] },
118
+ getAll: { level: 'permission', permissions: ['product.read'] },
119
+ getById: { level: 'permission', permissions: ['product.read'] },
120
+ update: { level: 'permission', permissions: ['product.update'] },
121
+ updateMany: { level: 'permission', permissions: ['product.update'] },
122
+ delete: { level: 'permission', permissions: ['product.delete'] },
123
+ bulkUpsert: { level: 'permission', permissions: ['product.create'] },
124
+ getByIds: { level: 'permission', permissions: ['product.read'] },
125
+ getByFilter: { level: 'permission', permissions: ['product.read'] },
362
126
  },
363
- ) {}
364
- ```
365
-
366
- ---
367
-
368
- ## Decorators
369
-
370
- ### @CurrentUser
371
-
372
- Extract the logged-in user from request:
373
-
374
- ```typescript
375
- import { CurrentUser } from '@flusys/nestjs-shared/decorators';
376
- import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
377
-
378
- @Controller('profile')
379
- export class ProfileController {
380
- @Post('me')
381
- getProfile(@CurrentUser() user: ILoggedUserInfo) {
382
- return { userId: user.id, companyId: user.companyId };
383
- }
384
-
385
- // Extract specific property
386
- @Post('id')
387
- getUserId(@CurrentUser('id') userId: string) {
388
- return { userId };
127
+ }) {
128
+ constructor(@Inject(ProductService) public override service: ProductService) {
129
+ super(service);
389
130
  }
390
131
  }
391
132
  ```
392
133
 
393
- ### @Public
394
-
395
- Mark route as public (skip authentication). **Use sparingly - security risk**.
396
-
397
- ```typescript
398
- import { Public } from '@flusys/nestjs-shared/decorators';
134
+ Generated POST-only endpoints: `/insert`, `/insert-many`, `/get-all`, `/get/:id`,
135
+ `/get-by-ids`, `/get-by-filter`, `/update`, `/update-many`, `/bulk-upsert`, `/delete`.
399
136
 
400
- @Controller('auth')
401
- export class AuthController {
402
- @Public()
403
- @Post('login')
404
- login() { }
405
- }
406
- ```
137
+ Security levels: `'public'` (no auth), `'jwt'` (token only), `'permission'` (token + action check).
407
138
 
408
- ### @RequirePermission
409
-
410
- Require specific permission(s) - **AND logic** by default:
411
-
412
- ```typescript
413
- import { RequirePermission } from '@flusys/nestjs-shared/decorators';
414
-
415
- @Controller('admin')
416
- export class AdminController {
417
- // Requires 'admin.dashboard' permission
418
- @RequirePermission('admin.dashboard')
419
- @Post('dashboard')
420
- getDashboard() { }
421
-
422
- // Requires BOTH permissions
423
- @RequirePermission('users.read', 'admin.access')
424
- @Post('users')
425
- getUsers() { }
426
- }
427
- ```
428
-
429
- ### @RequireAnyPermission
430
-
431
- Require any of the listed permissions - **OR logic**:
432
-
433
- ```typescript
434
- import { RequireAnyPermission } from '@flusys/nestjs-shared/decorators';
435
-
436
- @Controller('reports')
437
- export class ReportsController {
438
- // Requires 'reports.view' OR 'reports.admin'
439
- @RequireAnyPermission('reports.view', 'reports.admin')
440
- @Post()
441
- getReports() { }
442
- }
443
- ```
444
-
445
- ### @RequirePermissionCondition
446
-
447
- Build complex permission conditions with nested AND/OR logic:
448
-
449
- ```typescript
450
- import { RequirePermissionCondition } from '@flusys/nestjs-shared/decorators';
451
-
452
- @Controller('sensitive')
453
- export class SensitiveController {
454
- // Complex: User needs 'users.read' AND ('admin' OR 'manager')
455
- @RequirePermissionCondition({
456
- operator: 'and',
457
- permissions: ['users.read'],
458
- children: [
459
- { operator: 'or', permissions: ['admin', 'manager'] }
460
- ]
461
- })
462
- @Post('complex')
463
- getComplexData() { }
464
- }
465
- ```
466
-
467
- ### @SanitizeHtml / @SanitizeAndTrim
468
-
469
- Escape HTML entities for XSS prevention:
470
-
471
- ```typescript
472
- import { SanitizeHtml, SanitizeAndTrim } from '@flusys/nestjs-shared/decorators';
473
-
474
- export class CreateCommentDto {
475
- @SanitizeHtml()
476
- @IsString()
477
- content: string;
478
-
479
- @SanitizeAndTrim() // Escapes HTML AND trims whitespace
480
- @IsString()
481
- title: string;
482
- }
483
- ```
484
-
485
- ### @ApiResponseDto
486
-
487
- Generates Swagger schema for response:
488
-
489
- ```typescript
490
- import { ApiResponseDto } from '@flusys/nestjs-shared/decorators';
491
-
492
- @Controller('users')
493
- export class UserController {
494
- @Post('get-all')
495
- @ApiResponseDto(UserResponseDto, true, 'list') // Array with PaginationMetaDto
496
- getAll() { }
497
-
498
- @Post('insert')
499
- @ApiResponseDto(UserResponseDto, false) // Single item
500
- insert() { }
501
- }
502
- ```
503
-
504
- ---
505
-
506
- ## Guards
507
-
508
- ### JwtAuthGuard
509
-
510
- Validates JWT tokens for protected routes. Extends Passport's `AuthGuard('jwt')` and respects `@Public()` decorator.
139
+ ## 4. Guards and Decorators
511
140
 
512
141
  ```typescript
513
142
  import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
514
-
515
- // Apply globally in main.ts
516
- @Module({
517
- providers: [{ provide: APP_GUARD, useClass: JwtAuthGuard }],
518
- })
519
- export class AppModule {}
520
- ```
521
-
522
- **Important:** Constructor needs `@Inject(Reflector)` for bundled code.
523
-
524
- ### PermissionGuard
525
-
526
- Checks user permissions from cache with AND/OR/nested logic support.
527
-
528
- ```typescript
529
143
  import { PermissionGuard } from '@flusys/nestjs-shared/guards';
530
-
531
- @Module({
532
- providers: [
533
- { provide: APP_GUARD, useClass: PermissionGuard },
534
- {
535
- provide: 'PERMISSION_GUARD_CONFIG',
536
- useValue: {
537
- enableCompanyFeature: true,
538
- userPermissionKeyFormat: 'permissions:user:{userId}',
539
- companyPermissionKeyFormat: 'permissions:company:{companyId}:branch:{branchId}:user:{userId}',
540
- },
541
- },
542
- ],
543
- })
544
- export class AppModule {}
545
- ```
546
-
547
- **Cache Key Formats:**
548
-
549
- ```typescript
550
- // Without company feature
551
- `permissions:user:{userId}`
552
-
553
- // With company feature
554
- `permissions:company:{companyId}:branch:{branchId}:user:{userId}`
555
- ```
556
-
557
- ### Permission Exceptions
558
-
559
- ```typescript
560
144
  import {
561
- InsufficientPermissionsException, // 403 - Missing permissions
562
- NoPermissionsFoundException, // 403 - No permissions in cache
563
- PermissionSystemUnavailableException, // 500 - Cache unavailable
564
- } from '@flusys/nestjs-shared/exceptions';
565
- ```
566
-
567
- ---
145
+ CurrentUser,
146
+ Public,
147
+ RequirePermission,
148
+ RequireAnyPermission,
149
+ } from '@flusys/nestjs-shared/decorators';
150
+ import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
568
151
 
569
- ## Middleware
152
+ @UseGuards(JwtAuthGuard, PermissionGuard)
153
+ @RequirePermission('product.create')
154
+ @Post('insert')
155
+ async insert(@Body() dto: CreateProductDto, @CurrentUser() user: ILoggedUserInfo) { }
570
156
 
571
- ### LoggerMiddleware
157
+ // Mark endpoint public (no JWT required)
158
+ @Public()
159
+ @Post('public-list')
160
+ async publicList() { }
161
+ ```
572
162
 
573
- Combined middleware for request correlation and HTTP logging.
163
+ `ILoggedUserInfo` fields: `id`, `email`, `name?`, `phone?`, `profilePictureId?`, `companyId?`, `branchId?`.
574
164
 
575
- **Features:**
576
- - Request ID generation/tracking (UUID or from `x-request-id` header)
577
- - Tenant ID tracking (from `x-tenant-id` header)
578
- - AsyncLocalStorage context for thread-safe access
579
- - Automatic sensitive header redaction (authorization, cookie, x-api-key)
580
- - Performance monitoring (warns on requests > 3s)
581
- - Body truncation (max 1000 chars)
165
+ ## 5. Response DTOs
582
166
 
583
- **Usage:**
167
+ All controllers return one of these shapes:
584
168
 
585
169
  ```typescript
586
- import { LoggerMiddleware } from '@flusys/nestjs-shared/middlewares';
170
+ import {
171
+ SingleResponseDto, // insert, update, getById
172
+ ListResponseDto, // getAll, getByIds
173
+ BulkResponseDto, // insertMany, updateMany, bulkUpsert
174
+ MessageResponseDto, // delete
175
+ } from '@flusys/nestjs-shared/dtos';
587
176
 
588
- @Module({})
589
- export class AppModule implements NestModule {
590
- configure(consumer: MiddlewareConsumer) {
591
- consumer.apply(LoggerMiddleware).forRoutes('*');
592
- }
593
- }
177
+ // All shapes include: success, message, messageKey (for i18n), _meta (requestId, timestamp, duration)
178
+ // ListResponseDto / BulkResponseDto also include: meta (total, page, pageSize, count, totalPages)
594
179
  ```
595
180
 
596
- **Accessing Request Context:**
181
+ ## 6. Exceptions
597
182
 
598
183
  ```typescript
599
184
  import {
600
- getRequestId,
601
- getTenantId,
602
- getUserId,
603
- getCompanyId,
604
- setUserId,
605
- setCompanyId,
606
- } from '@flusys/nestjs-shared/middlewares';
185
+ NotFoundException,
186
+ ConflictException,
187
+ UnauthorizedException,
188
+ ForbiddenException,
189
+ ValidationException,
190
+ InternalServerException,
191
+ } from '@flusys/nestjs-shared/exceptions';
607
192
 
608
- @Injectable()
609
- export class MyService {
610
- async doSomething() {
611
- const requestId = getRequestId();
612
- const tenantId = getTenantId();
613
- setUserId('user-123');
614
- }
615
- }
193
+ throw new NotFoundException({
194
+ message: 'User not found',
195
+ messageKey: USER_MESSAGES.NOT_FOUND,
196
+ });
197
+ throw new ConflictException({
198
+ message: `User already assigned to this ${typeName}. Please refresh and try again.`,
199
+ messageKey: USER_PERMISSION_MESSAGES.ALREADY_ASSIGNED,
200
+ messageVariables: { type: typeName },
201
+ });
202
+ throw new ValidationException([{ field: 'email', message: 'Invalid format' }]);
616
203
  ```
617
204
 
618
- ---
619
-
620
- ## Interceptors
621
-
622
- ### ResponseMetaInterceptor
205
+ All exceptions produce: `{ success: false, message, messageKey,messageVariables:{} code, _meta }`.
623
206
 
624
- Adds `_meta` to all responses:
625
-
626
- ```typescript
627
- // Response includes:
628
- {
629
- "data": [...],
630
- "_meta": {
631
- "requestId": "abc-123",
632
- "timestamp": "2024-01-01T00:00:00.000Z",
633
- "responseTime": 45
634
- }
635
- }
636
- ```
637
-
638
- ### IdempotencyInterceptor
639
-
640
- Prevents duplicate POST requests using `X-Idempotency-Key` header. Caches responses for 24 hours.
641
-
642
- ### SetCreatedByOnBody / SetUpdateByOnBody / SetDeletedByOnBody
643
-
644
- Auto-set audit user IDs on request body from authenticated user.
645
-
646
- ### DeleteEmptyIdFromBodyInterceptor
647
-
648
- Removes empty `id` fields from request body (single and array bodies).
649
-
650
- ### QueryPerformanceInterceptor
651
-
652
- Monitors execution time and warns if request > 1000ms (configurable).
653
-
654
- ### Slug Interceptor
655
-
656
- Auto-generates `slug` from `name` field using `UtilsService.transformToSlug()`.
657
-
658
- ---
659
-
660
- ## Caching System
661
-
662
- ### HybridCache
663
-
664
- Two-tier caching with in-memory (L1) and Redis (L2):
207
+ ## 7. Hybrid Cache
665
208
 
666
209
  ```typescript
667
210
  import { HybridCache } from '@flusys/nestjs-shared/classes';
@@ -671,531 +214,24 @@ export class MyService {
671
214
  constructor(@Inject('CACHE_INSTANCE') private cache: HybridCache) {}
672
215
 
673
216
  async getData(key: string) {
674
- // Check cache first
675
217
  const cached = await this.cache.get(key);
676
218
  if (cached) return cached;
677
-
678
219
  const data = await this.fetchFromDb();
679
- await this.cache.set(key, data, 3600); // TTL in seconds
220
+ await this.cache.set(key, data, 3600); // TTL in seconds
680
221
  return data;
681
222
  }
682
223
 
683
224
  async invalidate(key: string) {
684
- await this.cache.del(key);
225
+ await this.cache.delete(key);
685
226
  }
686
-
687
- async invalidateAll() {
688
- await this.cache.reset(); // Clear L1
689
- await this.cache.resetL2(); // Clear L2 (Redis)
227
+ async invalidatePrefix(prefix: string) {
228
+ await this.cache.deleteByPrefix(prefix);
690
229
  }
691
230
  }
692
231
  ```
693
232
 
694
- ### CacheModule Setup
695
-
696
- ```typescript
697
- import { CacheModule } from '@flusys/nestjs-shared/modules';
698
-
699
- @Module({
700
- imports: [
701
- CacheModule.forRoot(
702
- true, // isGlobal
703
- 60_000, // memoryTtl (ms)
704
- 5000 // memorySize (LRU max items)
705
- ),
706
- ],
707
- })
708
- export class AppModule {}
709
- ```
710
-
711
- **Configuration via `USE_CACHE_LABEL` env:**
712
- - `'memory'` - L1 only
713
- - `'redis'` - L2 only
714
- - `'hybrid'` - Both (default)
715
-
716
- ---
717
-
718
- ## Multi-Tenant DataSource
719
-
720
- Dynamic database connection management with connection pooling.
721
-
722
- ### Setup
723
-
724
- ```typescript
725
- import { DataSourceModule } from '@flusys/nestjs-shared/modules';
726
-
727
- @Module({
728
- imports: [
729
- DataSourceModule.forRoot({
730
- bootstrapAppConfig: { databaseMode: 'multi-tenant' },
731
- defaultDatabaseConfig: {
732
- type: 'mysql',
733
- host: 'localhost',
734
- port: 3306,
735
- username: 'root',
736
- password: 'password',
737
- },
738
- tenants: [
739
- { id: 'tenant1', database: 'tenant1_db' },
740
- { id: 'tenant2', database: 'tenant2_db', host: 'other-server.com' },
741
- ],
742
- }),
743
- ],
744
- })
745
- export class AppModule {}
746
- ```
747
-
748
- ### MultiTenantDataSourceService
749
-
750
- | Method | Description |
751
- |--------|-------------|
752
- | `getDataSource()` | Get DataSource for current tenant (from header) |
753
- | `getDataSourceForTenant(id)` | Get DataSource for specific tenant |
754
- | `getRepository(entity)` | Get repository for current tenant |
755
- | `withTenant(id, callback)` | Execute callback with specific tenant |
756
- | `forAllTenants(callback)` | Execute callback for all tenants |
757
- | `registerTenant(config)` | Register new tenant at runtime |
758
- | `removeTenant(id)` | Remove tenant and close connection |
759
- | `getCurrentTenantId()` | Get tenant ID from request header |
760
-
761
- ---
762
-
763
- ## DTOs
764
-
765
- ### FilterAndPaginationDto
766
-
767
- ```typescript
768
- import { FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
769
-
770
- // Request body
771
- {
772
- "filter": { "status": "active" },
773
- "pagination": { "currentPage": 0, "pageSize": 10 },
774
- "sort": { "createdAt": "DESC" },
775
- "select": ["id", "name", "email"],
776
- "withDeleted": false
777
- }
778
- ```
779
-
780
- ### DeleteDto
781
-
782
- ```typescript
783
- import { DeleteDto } from '@flusys/nestjs-shared/dtos';
784
-
785
- // Soft delete
786
- { "id": "uuid", "type": "delete" }
787
-
788
- // Restore
789
- { "id": "uuid", "type": "restore" }
790
-
791
- // Permanent delete
792
- { "id": "uuid", "type": "permanent" }
793
-
794
- // Multiple IDs
795
- { "id": ["uuid1", "uuid2"], "type": "delete" }
796
- ```
797
-
798
- ### Response DTOs
799
-
800
- ```typescript
801
- // Single item
802
- class SingleResponseDto<T> {
803
- success: boolean;
804
- message: string;
805
- data?: T;
806
- _meta?: RequestMetaDto;
807
- }
808
-
809
- // Paginated list
810
- class ListResponseDto<T> {
811
- success: boolean;
812
- message: string;
813
- data?: T[];
814
- meta: PaginationMetaDto; // { total, page, pageSize, count, hasMore?, totalPages? }
815
- _meta?: RequestMetaDto;
816
- }
817
-
818
- // Bulk operations
819
- class BulkResponseDto<T> {
820
- success: boolean;
821
- message: string;
822
- data?: T[];
823
- meta: BulkMetaDto; // { count, failed?, total? }
824
- _meta?: RequestMetaDto;
825
- }
826
-
827
- // Message only
828
- class MessageResponseDto {
829
- success: boolean;
830
- message: string;
831
- _meta?: RequestMetaDto;
832
- }
833
- ```
834
-
835
- ---
836
-
837
- ## Base Entities
838
-
839
- ### Identity Entity
840
-
841
- Base entity with UUID and timestamps:
842
-
843
- ```typescript
844
- import { Identity } from '@flusys/nestjs-shared/entities';
845
-
846
- @Entity()
847
- export class Product extends Identity {
848
- // Inherited: id, createdAt, updatedAt, deletedAt
849
- // Inherited: createdById, updatedById, deletedById
850
-
851
- @Column()
852
- name: string;
853
- }
854
- ```
855
-
856
- ### UserRoot Entity
857
-
858
- Base user entity with common fields:
859
-
860
- ```typescript
861
- import { UserRoot } from '@flusys/nestjs-shared/entities';
862
-
863
- @Entity()
864
- export class User extends UserRoot {
865
- // Inherited: id, name, email, phone, profilePictureId
866
- // Inherited: isActive, emailVerified, phoneVerified
867
- // Inherited: lastLoginAt, additionalFields
868
- // Inherited: createdAt, updatedAt, deletedAt
869
- }
870
- ```
871
-
872
- ---
873
-
874
- ## Utilities
875
-
876
- ### Query Helpers
877
-
878
- ```typescript
879
- import {
880
- applyCompanyFilter,
881
- buildCompanyWhereCondition,
882
- hasCompanyId,
883
- validateCompanyOwnership,
884
- } from '@flusys/nestjs-shared/utils';
885
-
886
- // Add company filter to TypeORM query
887
- applyCompanyFilter(query, {
888
- isCompanyFeatureEnabled: true,
889
- entityAlias: 'entity',
890
- }, user);
891
-
892
- // Build where condition for company
893
- const where = buildCompanyWhereCondition(baseWhere, user, isCompanyFeatureEnabled);
894
-
895
- // Validate entity belongs to user's company
896
- validateCompanyOwnership(entity, user, 'Entity');
897
- ```
898
-
899
- ### String Utilities
900
-
901
- ```typescript
902
- import { generateSlug, generateUniqueSlug } from '@flusys/nestjs-shared/utils';
903
-
904
- // Generate URL-friendly slug
905
- const slug = generateSlug('My Product Name', 100);
906
- // Returns: 'my-product-name'
907
-
908
- // Generate unique slug with collision detection
909
- const uniqueSlug = await generateUniqueSlug(
910
- 'My Product',
911
- async (pattern) => existingSlugs.filter(s => s.startsWith(pattern)),
912
- 100
913
- );
914
- ```
915
-
916
- ### Request Utilities
917
-
918
- ```typescript
919
- import {
920
- isBrowserRequest,
921
- buildCookieOptions,
922
- parseDurationToMs,
923
- } from '@flusys/nestjs-shared/utils';
924
-
925
- // Detect browser vs API client
926
- const isBrowser = isBrowserRequest(req);
927
-
928
- // Build secure cookie options
929
- const cookieOpts = buildCookieOptions(req);
930
-
931
- // Parse duration string
932
- const ms = parseDurationToMs('7d'); // 604800000
933
- ```
934
-
935
- ### HTML Sanitizer
936
-
937
- ```typescript
938
- import { escapeHtml, escapeHtmlVariables } from '@flusys/nestjs-shared/utils';
939
-
940
- // Escape single string
941
- const safe = escapeHtml('<script>alert("xss")</script>');
942
- // Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
943
-
944
- // Escape all values in an object
945
- const safeVars = escapeHtmlVariables({
946
- userName: '<script>evil</script>',
947
- message: 'Hello, World!',
948
- });
949
- ```
950
-
951
- ---
952
-
953
- ## Error Handling
954
-
955
- ### Error Handler Utilities
956
-
957
- ```typescript
958
- import {
959
- getErrorMessage,
960
- logError,
961
- rethrowError,
962
- logAndRethrow,
963
- } from '@flusys/nestjs-shared/utils';
964
-
965
- interface IErrorContext {
966
- operation?: string;
967
- entity?: string;
968
- userId?: string;
969
- id?: string;
970
- companyId?: string;
971
- data?: Record<string, unknown>;
972
- }
973
-
974
- // Safe error message extraction
975
- const message = getErrorMessage(error);
976
-
977
- // Log with sensitive key redaction (password, secret, token, apiKey)
978
- logError(logger, error, 'createUser', { userId: user.id });
979
-
980
- // Type-safe rethrow
981
- rethrowError(error);
982
-
983
- // Combined log + rethrow
984
- logAndRethrow(logger, error, 'updateUser', context);
985
- ```
986
-
987
- ---
988
-
989
- ## Constants
990
-
991
- ### Permission Constants
992
-
993
- ```typescript
994
- import { PERMISSIONS } from '@flusys/nestjs-shared/constants';
995
-
996
- // Organized by module:
997
- PERMISSIONS.USER.CREATE // 'user.create'
998
- PERMISSIONS.USER.READ // 'user.read'
999
- PERMISSIONS.USER.UPDATE // 'user.update'
1000
- PERMISSIONS.USER.DELETE // 'user.delete'
1001
-
1002
- PERMISSIONS.COMPANY.CREATE // 'company.create'
1003
- PERMISSIONS.BRANCH.CREATE // 'branch.create'
1004
-
1005
- PERMISSIONS.ROLE.CREATE // 'role.create'
1006
- PERMISSIONS.FILE.CREATE // 'file.create'
1007
- PERMISSIONS.FOLDER.CREATE // 'folder.create'
1008
- // ...etc
1009
- ```
1010
-
1011
- ### Injection Tokens
1012
-
1013
- ```typescript
1014
- 'CACHE_INSTANCE' // HybridCache provider
1015
- 'PERMISSION_GUARD_CONFIG' // PermissionGuard config
1016
- 'LOGGER_INSTANCE' // Logger provider
1017
- ```
233
+ `CacheModule.forRoot(true)` connects to Redis automatically when `REDIS_URL` is set; otherwise uses in-memory only.
1018
234
 
1019
- ---
1020
-
1021
- ## API Reference
1022
-
1023
- ### Main Exports
1024
-
1025
- ```typescript
1026
- // Classes
1027
- import {
1028
- ApiService,
1029
- RequestScopedApiService,
1030
- createApiController,
1031
- HybridCache,
1032
- WinstonLoggerAdapter,
1033
- NestLoggerAdapter,
1034
- } from '@flusys/nestjs-shared/classes';
1035
-
1036
- // Decorators
1037
- import {
1038
- CurrentUser,
1039
- Public,
1040
- RequirePermission,
1041
- RequireAnyPermission,
1042
- RequirePermissionCondition,
1043
- SanitizeHtml,
1044
- SanitizeAndTrim,
1045
- ApiResponseDto,
1046
- } from '@flusys/nestjs-shared/decorators';
1047
-
1048
- // Guards
1049
- import { JwtAuthGuard, PermissionGuard } from '@flusys/nestjs-shared/guards';
1050
-
1051
- // Interceptors
1052
- import {
1053
- ResponseMetaInterceptor,
1054
- IdempotencyInterceptor,
1055
- SetCreatedByOnBody,
1056
- SetUpdateByOnBody,
1057
- SetDeletedByOnBody,
1058
- DeleteEmptyIdFromBodyInterceptor,
1059
- QueryPerformanceInterceptor,
1060
- Slug,
1061
- } from '@flusys/nestjs-shared/interceptors';
1062
-
1063
- // Modules
1064
- import {
1065
- CacheModule,
1066
- DataSourceModule,
1067
- UtilsModule,
1068
- UtilsService,
1069
- MultiTenantDataSourceService,
1070
- } from '@flusys/nestjs-shared/modules';
1071
-
1072
- // DTOs
1073
- import {
1074
- FilterAndPaginationDto,
1075
- PaginationDto,
1076
- DeleteDto,
1077
- GetByIdBodyDto,
1078
- SingleResponseDto,
1079
- ListResponseDto,
1080
- BulkResponseDto,
1081
- MessageResponseDto,
1082
- IdentityResponseDto,
1083
- PaginationMetaDto,
1084
- BulkMetaDto,
1085
- RequestMetaDto,
1086
- } from '@flusys/nestjs-shared/dtos';
1087
-
1088
- // Entities
1089
- import { Identity, UserRoot } from '@flusys/nestjs-shared/entities';
1090
-
1091
- // Interfaces
1092
- import {
1093
- ILoggedUserInfo,
1094
- IService,
1095
- IDataSourceProvider,
1096
- IModuleConfigService,
1097
- ILogger,
1098
- PermissionCondition,
1099
- PermissionOperator,
1100
- } from '@flusys/nestjs-shared/interfaces';
1101
-
1102
- // Utilities
1103
- import {
1104
- getErrorMessage,
1105
- logError,
1106
- applyCompanyFilter,
1107
- buildCompanyWhereCondition,
1108
- validateCompanyOwnership,
1109
- generateSlug,
1110
- generateUniqueSlug,
1111
- isBrowserRequest,
1112
- buildCookieOptions,
1113
- parseDurationToMs,
1114
- escapeHtml,
1115
- escapeHtmlVariables,
1116
- } from '@flusys/nestjs-shared/utils';
1117
-
1118
- // Middleware
1119
- import {
1120
- LoggerMiddleware,
1121
- getRequestId,
1122
- getTenantId,
1123
- getUserId,
1124
- getCompanyId,
1125
- setUserId,
1126
- setCompanyId,
1127
- } from '@flusys/nestjs-shared/middlewares';
1128
-
1129
- // Exceptions
1130
- import {
1131
- InsufficientPermissionsException,
1132
- NoPermissionsFoundException,
1133
- PermissionSystemUnavailableException,
1134
- } from '@flusys/nestjs-shared/exceptions';
1135
-
1136
- // Constants
1137
- import { PERMISSIONS } from '@flusys/nestjs-shared/constants';
1138
- ```
1139
-
1140
- ---
1141
-
1142
- ## Best Practices
1143
-
1144
- ### 1. Use Generic Service Pattern
1145
-
1146
- ```typescript
1147
- // Extend ApiService for consistent CRUD
1148
- @Injectable()
1149
- export class ProductService extends ApiService<...> {
1150
- // Override hooks for customization
1151
- }
1152
-
1153
- // For dynamic entities, use RequestScopedApiService
1154
- @Injectable({ scope: Scope.REQUEST })
1155
- export class RoleService extends RequestScopedApiService<...> { }
1156
- ```
1157
-
1158
- ### 2. Always Use @Inject() Decorators
1159
-
1160
- Required for esbuild bundled code:
1161
-
1162
- ```typescript
1163
- // CORRECT
1164
- constructor(
1165
- @Inject(MyService) private readonly myService: MyService,
1166
- @Inject('CACHE_INSTANCE') private readonly cache: HybridCache,
1167
- ) {}
1168
-
1169
- // WRONG - fails in bundled code
1170
- constructor(private readonly myService: MyService) {}
1171
- ```
1172
-
1173
- ### 3. Use Decorators Consistently
1174
-
1175
- ```typescript
1176
- // Use built-in decorators
1177
- @CurrentUser() user: ILoggedUserInfo
1178
- @RequirePermission('users.create')
1179
- @Public() // Use sparingly!
1180
-
1181
- // Don't access request directly
1182
- @Req() req: Request // Not type-safe
1183
- ```
1184
-
1185
- ### 4. Configure Security at Controller Level
1186
-
1187
- ```typescript
1188
- // GOOD - configure in createApiController
1189
- export class UserController extends createApiController(..., {
1190
- security: { insert: 'permission', ... },
1191
- }) {}
1192
-
1193
- // AVOID - adding guards to each endpoint
1194
- @UseGuards(JwtGuard)
1195
- @Post('create')
1196
- create() {}
1197
- ```
1198
-
1199
- ---
235
+ ## License
1200
236
 
1201
- **Last Updated:** 2026-02-21
237
+ MIT © FLUSYS