@flusys/nestjs-shared 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Shared Package Guide
2
2
 
3
3
  > **Package:** `@flusys/nestjs-shared`
4
+ > **Version:** 3.0.0
4
5
  > **Type:** Shared NestJS utilities, classes, decorators, guards, and modules
5
6
 
6
7
  This comprehensive guide covers the shared package - the shared NestJS infrastructure layer.
@@ -23,6 +24,7 @@ This comprehensive guide covers the shared package - the shared NestJS infrastru
23
24
  - [Utilities](#utilities)
24
25
  - [Error Handling](#error-handling)
25
26
  - [Constants](#constants)
27
+ - [Logger System](#logger-system)
26
28
  - [API Reference](#api-reference)
27
29
 
28
30
  ---
@@ -32,13 +34,14 @@ This comprehensive guide covers the shared package - the shared NestJS infrastru
32
34
  `@flusys/nestjs-shared` provides shared utilities for building scalable NestJS applications:
33
35
 
34
36
  - **Generic CRUD** - Standardized API controller and service patterns
35
- - **Permission System** - Role and permission-based access control with complex logic
37
+ - **Permission System** - Role and permission-based access control with complex logic and wildcard matching
36
38
  - **Caching** - In-memory + Redis hybrid caching (HybridCache)
37
- - **Request Correlation** - AsyncLocalStorage-based request tracking
39
+ - **Request Correlation** - AsyncLocalStorage-based request tracking with correlation IDs
38
40
  - **Middleware** - Logging, correlation, and performance monitoring
39
41
  - **Interceptors** - Response metadata, idempotency, auto field setting
40
- - **Multi-Tenancy** - Dynamic database connection management
42
+ - **Multi-Tenancy** - Dynamic database connection management with connection pooling
41
43
  - **Error Handling** - Centralized error handling with sensitive data redaction
44
+ - **Logging** - Winston-based logging with tenant-aware routing and daily rotation
42
45
 
43
46
  ### Package Hierarchy
44
47
 
@@ -63,35 +66,34 @@ nestjs-shared/
63
66
  ├── src/
64
67
  │ ├── classes/ # Base classes
65
68
  │ │ ├── api-service.class.ts # Generic CRUD service
66
- │ │ ├── api-controller.class.ts # Generic CRUD controller factory
69
+ │ │ ├── api-controller.class.ts # Generic CRUD controller factory (createApiController)
67
70
  │ │ ├── request-scoped-api.service.ts # REQUEST-scoped service base
68
- │ │ ├── hybrid-cache.class.ts # Two-tier caching
69
- │ │ ├── winston.logger.class.ts # Winston logger config
70
- │ │ ├── winston-logger-adapter.class.ts
71
- │ │ └── nest-logger-adapter.class.ts
71
+ │ │ ├── hybrid-cache.class.ts # Two-tier caching (memory + Redis)
72
+ │ │ ├── winston.logger.class.ts # Winston logger config with tenant routing
73
+ │ │ └── winston-logger-adapter.class.ts # ILogger adapters
72
74
  │ │
73
75
  │ ├── constants/ # Injection tokens & constants
74
- │ │ ├── permissions.ts # Permission constants
75
- │ │ └── index.ts
76
+ │ │ ├── permissions.ts # Permission constants (PERMISSIONS)
77
+ │ │ └── index.ts # Metadata keys, injection tokens, headers
76
78
  │ │
77
79
  │ ├── decorators/ # Custom decorators
78
80
  │ │ ├── api-response.decorator.ts # @ApiResponseDto
79
81
  │ │ ├── current-user.decorator.ts # @CurrentUser
80
82
  │ │ ├── public.decorator.ts # @Public
81
- │ │ ├── require-permission.decorator.ts # @RequirePermission
82
- │ │ ├── sanitize.decorator.ts # @SanitizeHtml, @SanitizeAndTrim
83
+ │ │ ├── require-permission.decorator.ts # @RequirePermission, @RequireAnyPermission, @RequirePermissionLogic
84
+ │ │ ├── sanitize-html.decorator.ts # @SanitizeHtml, @SanitizeAndTrim
83
85
  │ │ └── index.ts
84
86
  │ │
85
87
  │ ├── 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
88
+ │ │ ├── delete.dto.ts # DeleteDto with soft/restore/permanent
89
+ │ │ ├── filter-and-pagination.dto.ts # FilterAndPaginationDto, GetByIdBodyDto
90
+ │ │ ├── identity-response.dto.ts # IdentityResponseDto
91
+ │ │ ├── pagination.dto.ts # PaginationDto
92
+ │ │ ├── response-payload.dto.ts # Single/List/Bulk/Message response DTOs
91
93
  │ │ └── index.ts
92
94
  │ │
93
95
  │ ├── entities/ # Base entities
94
- │ │ ├── identity.ts # Base entity with UUID
96
+ │ │ ├── identity.ts # Base entity with UUID & audit fields
95
97
  │ │ ├── user-root.ts # Base user entity
96
98
  │ │ └── index.ts
97
99
  │ │
@@ -99,45 +101,44 @@ nestjs-shared/
99
101
  │ │ └── permission.exception.ts # Permission-related exceptions
100
102
  │ │
101
103
  │ ├── guards/ # Authentication & authorization
102
- │ │ ├── jwt-auth.guard.ts # JWT token validation
103
- │ │ └── permission.guard.ts # Permission checks
104
+ │ │ ├── jwt-auth.guard.ts # JWT token validation with @Public support
105
+ │ │ └── permission.guard.ts # Permission checks with wildcard matching
104
106
  │ │
105
107
  │ ├── interceptors/ # Request/response interceptors
106
108
  │ │ ├── delete-empty-id-from-body.interceptor.ts
107
- │ │ ├── idempotency.interceptor.ts
109
+ │ │ ├── idempotency.interceptor.ts # Prevents duplicate POST requests
108
110
  │ │ ├── query-performance.interceptor.ts
109
- │ │ ├── response-meta.interceptor.ts
110
- │ │ ├── set-create-by-on-body.interceptor.ts
111
- │ │ ├── set-delete-by-on-body.interceptor.ts
112
- │ │ ├── set-update-by-on-body.interceptor.ts
113
- │ │ └── slug.interceptor.ts
111
+ │ │ ├── response-meta.interceptor.ts # Adds _meta to responses
112
+ │ │ ├── set-user-field-on-body.interceptor.ts # Factory + SetCreatedByOnBody, etc.
113
+ │ │ └── slug.interceptor.ts # Auto-generates slug from name
114
114
  │ │
115
115
  │ ├── interfaces/ # TypeScript interfaces
116
116
  │ │ ├── api.interface.ts # IService interface
117
- │ │ ├── identity.interface.ts
117
+ │ │ ├── datasource.interface.ts # IDataSourceProvider
118
+ │ │ ├── identity.interface.ts # IIdentity interface
118
119
  │ │ ├── logged-user-info.interface.ts # ILoggedUserInfo
119
- │ │ ├── logger.interface.ts # ILogger
120
- │ │ ├── permission.interface.ts # PermissionCondition
121
- │ │ └── datasource-provider.interface.ts
120
+ │ │ ├── logger.interface.ts # ILogger interface
121
+ │ │ ├── module-config.interface.ts # IModuleConfigService
122
+ │ │ └── permission.interface.ts # ILogicNode, PermissionConfig, PermissionGuardConfig
122
123
  │ │
123
124
  │ ├── middlewares/ # Middleware
124
- │ │ └── logger.middleware.ts # Request logging & correlation
125
+ │ │ └── logger.middleware.ts # Request correlation with AsyncLocalStorage
125
126
  │ │
126
127
  │ ├── modules/ # NestJS modules
127
- │ │ ├── cache/cache.module.ts
128
+ │ │ ├── cache/cache.module.ts # CacheModule.forRoot()
128
129
  │ │ ├── datasource/
129
- │ │ │ ├── datasource.module.ts
130
+ │ │ │ ├── datasource.module.ts # DataSourceModule.forRoot/forRootAsync/forFeature
130
131
  │ │ │ └── multi-tenant-datasource.service.ts
131
132
  │ │ └── utils/
132
- │ │ ├── utils.module.ts
133
- │ │ └── utils.service.ts
133
+ │ │ ├── utils.module.ts # Global UtilsModule
134
+ │ │ └── utils.service.ts # Cache helpers, string utilities
134
135
  │ │
135
136
  │ └── utils/ # Utility functions
136
- │ ├── error-handler.util.ts
137
- │ ├── query-helpers.util.ts
138
- │ ├── string.util.ts
139
- │ ├── request.util.ts
140
- │ └── html-sanitizer.util.ts
137
+ │ ├── error-handler.util.ts # ErrorHandler class
138
+ │ ├── html-sanitizer.util.ts # escapeHtml, escapeHtmlVariables
139
+ │ ├── query-helpers.util.ts # applyCompanyFilter, validateCompanyOwnership
140
+ │ ├── request.util.ts # isBrowserRequest, buildCookieOptions, parseDurationToMs
141
+ │ └── string.util.ts # generateSlug, generateUniqueSlug
141
142
  ```
142
143
 
143
144
  ---
@@ -149,10 +150,12 @@ The `ApiService` base class provides standardized CRUD operations with caching,
149
150
  ### Basic Usage
150
151
 
151
152
  ```typescript
152
- import { ApiService, HybridCache } from '@flusys/nestjs-shared/classes';
153
- import { UtilsService } from '@flusys/nestjs-shared/modules';
153
+ import { ApiService, HybridCache } from '@flusys/nestjs-shared';
154
+ import { UtilsService } from '@flusys/nestjs-shared';
154
155
  import { Injectable, Inject } from '@nestjs/common';
156
+ import { InjectRepository } from '@nestjs/typeorm';
155
157
  import { Repository } from 'typeorm';
158
+ import { CACHE_INSTANCE } from '@flusys/nestjs-shared';
156
159
 
157
160
  @Injectable()
158
161
  export class UserService extends ApiService<
@@ -165,17 +168,17 @@ export class UserService extends ApiService<
165
168
  constructor(
166
169
  @InjectRepository(User)
167
170
  protected override repository: Repository<User>,
168
- @Inject('CACHE_INSTANCE')
171
+ @Inject(CACHE_INSTANCE)
169
172
  protected override cacheManager: HybridCache,
170
173
  @Inject(UtilsService)
171
174
  protected override utilsService: UtilsService,
172
175
  ) {
173
176
  super(
174
- 'user', // Entity name (for query building)
177
+ 'user', // Entity name (for query alias)
175
178
  repository,
176
179
  cacheManager,
177
180
  utilsService,
178
- 'UserService', // Service name (for logging)
181
+ 'UserService', // Logger context name
179
182
  true, // Enable caching
180
183
  );
181
184
  }
@@ -184,71 +187,94 @@ export class UserService extends ApiService<
184
187
 
185
188
  ### ApiService Methods
186
189
 
187
- | Method | Description |
188
- |--------|-------------|
189
- | `insert(dto, user)` | Create single entity |
190
- | `insertMany(dtos, user)` | Create multiple entities |
191
- | `getById(id, user, select?)` | Get entity by ID |
192
- | `findById(id, user, select?)` | Find entity (returns null if not found) |
193
- | `findByIds(ids, user, select?)` | Find multiple by IDs |
194
- | `getAll(dto, user)` | Get paginated list |
195
- | `update(dto, user)` | Update single entity |
196
- | `updateMany(dtos, user)` | Update multiple entities |
197
- | `delete(dto, user)` | Soft/permanent delete or restore |
190
+ | Method | Signature | Description |
191
+ |--------|-----------|-------------|
192
+ | `insert` | `(dto, user) → Promise<T>` | Create single entity |
193
+ | `insertMany` | `(dtos[], user) → Promise<T[]>` | Create multiple entities |
194
+ | `findById` | `(id, user, select?) → Promise<T>` | Find entity by ID (throws if not found) |
195
+ | `findByIds` | `(ids[], user) → Promise<T[]>` | Find multiple by IDs |
196
+ | `getAll` | `(search, filterDto, user) → Promise<{data, total}>` | Get paginated list with filters |
197
+ | `update` | `(dto, user) → Promise<T>` | Update single entity |
198
+ | `updateMany` | `(dtos[], user) → Promise<T[]>` | Update multiple entities |
199
+ | `delete` | `(deleteDto, user) → Promise<null>` | Soft/permanent delete or restore |
200
+ | `clearCacheForAll` | `() → Promise<void>` | Clear all entity cache |
201
+ | `clearCacheForId` | `(entities[]) → Promise<void>` | Clear cache for specific entities |
198
202
 
199
203
  ### Customization Hooks
200
204
 
201
205
  ```typescript
202
206
  @Injectable()
203
207
  export class UserService extends ApiService<...> {
204
- // Convert DTO to entity
205
- override async convertSingleDtoToEntity(dto, user): Promise<User> { }
206
-
207
- // Customize SELECT query
208
- override async getSelectQuery(query, user, select?): Promise<{ query, isRaw }> { }
209
-
210
- // Add WHERE filters
211
- override async getFilterQuery(query, filter, user): Promise<{ query, isRaw }> { }
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 }> { }
208
+ // Repository initialization (for RequestScopedApiService)
209
+ protected async ensureRepositoryInitialized(): Promise<void> { }
210
+
211
+ // Convert DTO to entity (called for single item)
212
+ protected async convertSingleDtoToEntity(
213
+ dto: CreateDtoT | UpdateDtoT,
214
+ user: ILoggedUserInfo | null
215
+ ): Promise<EntityT> { }
216
+
217
+ // Convert entity to response DTO
218
+ protected convertEntityToResponseDto(entity: EntityT, isRaw: boolean): InterfaceT { }
219
+
220
+ // Convert entity list to response list
221
+ protected convertEntityListToResponseListDto(entities: EntityT[], isRaw: boolean): InterfaceT[] { }
222
+
223
+ // Customize SELECT query (add joins, selections)
224
+ protected async getSelectQuery(
225
+ query: SelectQueryBuilder<EntityT>,
226
+ user: ILoggedUserInfo | null,
227
+ select?: string[]
228
+ ): Promise<{ query: SelectQueryBuilder<EntityT>; isRaw: boolean }> { }
229
+
230
+ // Add WHERE filters from filter object
231
+ protected async getFilterQuery(
232
+ query: SelectQueryBuilder<EntityT>,
233
+ filter: Record<string, any>,
234
+ user: ILoggedUserInfo | null
235
+ ): Promise<{ query: SelectQueryBuilder<EntityT>; isRaw: boolean }> { }
236
+
237
+ // Add global search conditions (default: searches 'name' field)
238
+ protected async getGlobalSearchQuery(
239
+ query: SelectQueryBuilder<EntityT>,
240
+ search: string,
241
+ user: ILoggedUserInfo | null
242
+ ): Promise<{ query: SelectQueryBuilder<EntityT>; isRaw: boolean }> { }
243
+
244
+ // Add sort conditions
245
+ protected async getSortQuery(
246
+ query: SelectQueryBuilder<EntityT>,
247
+ sort: Record<string, 'ASC' | 'DESC'>,
248
+ user: ILoggedUserInfo | null
249
+ ): Promise<{ query: SelectQueryBuilder<EntityT>; isRaw: boolean }> { }
218
250
 
219
251
  // Add extra query conditions (e.g., company filtering)
220
- override async getExtraManipulateQuery(query, dto, user): Promise<{ query, isRaw }> { }
221
-
222
- // Before insert hook
223
- override async beforeInsertOperation(entity, user, queryRunner): Promise<void> { }
224
-
225
- // After insert hook
226
- override async afterInsertOperation(entity, user, queryRunner): Promise<void> { }
227
-
228
- // Before update hook
229
- override async beforeUpdateOperation(oldEntity, newEntity, user, queryRunner): Promise<void> { }
230
-
231
- // After update hook
232
- override async afterUpdateOperation(entity, user, queryRunner): Promise<void> { }
233
-
234
- // Before delete hook
235
- override async beforeDeleteOperation(dto, user, queryRunner): Promise<void> { }
236
-
237
- // After delete hook
238
- override async afterDeleteOperation(dto, user, queryRunner): Promise<void> { }
252
+ protected async getExtraManipulateQuery(
253
+ query: SelectQueryBuilder<EntityT>,
254
+ dto: FilterAndPaginationDto,
255
+ user: ILoggedUserInfo | null
256
+ ): Promise<{ query: SelectQueryBuilder<EntityT>; isRaw: boolean }> { }
257
+
258
+ // Lifecycle hooks
259
+ protected async beforeInsertOperation(dto, user, queryRunner): Promise<void> { }
260
+ protected async afterInsertOperation(entities[], user, queryRunner): Promise<void> { }
261
+ protected async beforeUpdateOperation(dto, user, queryRunner): Promise<void> { }
262
+ protected async afterUpdateOperation(entities[], user, queryRunner): Promise<void> { }
263
+ protected async beforeDeleteOperation(dto, user, queryRunner): Promise<void> { }
264
+ protected async afterDeleteOperation(entities[], user, queryRunner): Promise<void> { }
239
265
  }
240
- ```
241
266
 
242
267
  ---
243
268
 
244
269
  ## RequestScopedApiService
245
270
 
246
- For dynamic entity resolution based on runtime configuration (e.g., company feature).
271
+ For dynamic entity resolution based on runtime configuration (e.g., company feature toggling). Extends `ApiService` with lazy repository initialization.
247
272
 
248
273
  ```typescript
249
- import { RequestScopedApiService } from '@flusys/nestjs-shared/classes';
274
+ import { RequestScopedApiService, HybridCache, CACHE_INSTANCE } from '@flusys/nestjs-shared';
275
+ import { UtilsService, IDataSourceProvider } from '@flusys/nestjs-shared';
250
276
  import { Injectable, Scope, Inject } from '@nestjs/common';
251
- import { EntityTarget, Repository } from 'typeorm';
277
+ import { DataSource, EntityTarget, Repository } from 'typeorm';
252
278
 
253
279
  @Injectable({ scope: Scope.REQUEST })
254
280
  export class RoleService extends RequestScopedApiService<
@@ -258,8 +284,10 @@ export class RoleService extends RequestScopedApiService<
258
284
  RoleBase,
259
285
  Repository<RoleBase>
260
286
  > {
287
+ private actionRepository!: Repository<Action>;
288
+
261
289
  constructor(
262
- @Inject('CACHE_INSTANCE') protected override cacheManager: HybridCache,
290
+ @Inject(CACHE_INSTANCE) protected override cacheManager: HybridCache,
263
291
  @Inject(UtilsService) protected override utilsService: UtilsService,
264
292
  @Inject(ModuleConfigService) private readonly config: ModuleConfigService,
265
293
  @Inject(DataSourceProvider) private readonly provider: DataSourceProvider,
@@ -268,42 +296,55 @@ export class RoleService extends RequestScopedApiService<
268
296
  super('role', null as any, cacheManager, utilsService, 'RoleService', true);
269
297
  }
270
298
 
271
- // Required: Resolve which entity to use
299
+ // Required: Resolve which entity class to use based on runtime config
272
300
  protected resolveEntity(): EntityTarget<RoleBase> {
273
301
  return this.config.isCompanyFeatureEnabled() ? RoleWithCompany : Role;
274
302
  }
275
303
 
276
- // Required: Return the DataSource provider
304
+ // Required: Return the DataSource provider for repository creation
277
305
  protected getDataSourceProvider(): IDataSourceProvider {
278
306
  return this.provider;
279
307
  }
280
308
 
281
- // Optional: Initialize additional repositories
282
- protected async initializeAdditionalRepositories(): Promise<void> {
283
- this.actionRepository = await this.dataSourceProvider.getRepository(Action);
309
+ // Override to initialize additional repositories alongside primary
310
+ protected override async ensureRepositoryInitialized(): Promise<void> {
311
+ await super.ensureRepositoryInitialized();
312
+ // Initialize additional repositories if needed
313
+ const repos = await this.initializeAdditionalRepositories([Action]);
314
+ this.actionRepository = repos[0];
284
315
  }
285
316
  }
286
317
  ```
287
318
 
288
319
  ### Key Methods
289
320
 
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 |
321
+ | Method | Signature | Description |
322
+ |--------|-----------|-------------|
323
+ | `ensureRepositoryInitialized()` | `() Promise<void>` | Auto-called before operations, initializes repository |
324
+ | `resolveEntity()` | `() → EntityTarget<EntityT>` | **Abstract** - Return entity class based on runtime config |
325
+ | `getDataSourceProvider()` | `() → IDataSourceProvider` | **Abstract** - Return DataSource provider |
326
+ | `initializeAdditionalRepositories()` | `(entities[]) Promise<Repository[]>` | Initialize extra repositories |
327
+ | `getDataSourceForService()` | `() → Promise<DataSource>` | Get raw DataSource for transactions |
328
+
329
+ ### IDataSourceProvider Interface
330
+
331
+ ```typescript
332
+ interface IDataSourceProvider {
333
+ getRepository<T extends ObjectLiteral>(entity: EntityTarget<T>): Promise<Repository<T>>;
334
+ getDataSource(): Promise<DataSource>;
335
+ }
336
+ ```
296
337
 
297
338
  ---
298
339
 
299
340
  ## ApiController - Generic CRUD Controller
300
341
 
301
- The `createApiController` factory creates standardized POST-only RPC controllers.
342
+ The `createApiController` factory creates standardized POST-only RPC controllers with full Swagger documentation.
302
343
 
303
344
  ### Basic Usage
304
345
 
305
346
  ```typescript
306
- import { createApiController } from '@flusys/nestjs-shared/classes';
347
+ import { createApiController } from '@flusys/nestjs-shared';
307
348
  import { Controller, Inject } from '@nestjs/common';
308
349
  import { ApiTags } from '@nestjs/swagger';
309
350
 
@@ -324,26 +365,51 @@ export class UserController extends createApiController<
324
365
 
325
366
  ### Generated Endpoints
326
367
 
327
- | Endpoint | Method | Description |
328
- |----------|--------|-------------|
329
- | `/insert` | POST | Create entity |
330
- | `/insert-many` | POST | Create multiple entities |
331
- | `/get/:id` | POST | Get entity by ID |
332
- | `/get-all` | POST | Get paginated list |
333
- | `/update` | POST | Update entity |
334
- | `/update-many` | POST | Update multiple entities |
335
- | `/delete` | POST | Delete entity |
368
+ | Endpoint | Method | Description | Interceptors |
369
+ |----------|--------|-------------|--------------|
370
+ | `/insert` | POST | Create entity | Idempotency, SetCreatedByOnBody, Slug |
371
+ | `/insert-many` | POST | Create multiple entities | Idempotency, SetCreatedByOnBody, Slug |
372
+ | `/get/:id` | POST | Get entity by ID | - |
373
+ | `/get-all` | POST | Get paginated list with filters | - |
374
+ | `/update` | POST | Update entity | SetUpdateByOnBody, Slug |
375
+ | `/update-many` | POST | Update multiple entities | SetUpdateByOnBody, Slug |
376
+ | `/delete` | POST | Delete/restore/permanent delete | SetDeletedByOnBody |
377
+
378
+ ### Security Configuration Types
379
+
380
+ ```typescript
381
+ // Security levels
382
+ type SecurityLevel = 'public' | 'jwt' | 'permission';
383
+
384
+ // Endpoint names
385
+ type ApiEndpoint = 'insert' | 'insertMany' | 'getById' | 'getAll' | 'update' | 'updateMany' | 'delete';
386
+
387
+ // Endpoint security config
388
+ interface EndpointSecurity {
389
+ level: SecurityLevel;
390
+ permissions?: string[]; // Required permissions
391
+ operator?: 'AND' | 'OR'; // How to combine permissions (default: AND)
392
+ logic?: IPermissionLogic; // Complex permission logic tree
393
+ }
394
+
395
+ // Per-endpoint or global config
396
+ type ApiSecurityConfig = { [K in ApiEndpoint]?: EndpointSecurity | SecurityLevel };
397
+
398
+ interface ApiControllerOptions {
399
+ security?: ApiSecurityConfig | EndpointSecurity | SecurityLevel;
400
+ }
401
+ ```
336
402
 
337
- ### Security Configuration
403
+ ### Security Configuration Examples
338
404
 
339
405
  ```typescript
340
- // Global security (all endpoints)
406
+ // Global security - all endpoints require JWT
341
407
  @Controller('users')
342
408
  export class UserController extends createApiController(
343
409
  CreateUserDto,
344
410
  UpdateUserDto,
345
411
  UserResponseDto,
346
- { security: 'jwt' }, // All endpoints require JWT
412
+ { security: 'jwt' },
347
413
  ) {}
348
414
 
349
415
  // Per-endpoint security
@@ -356,14 +422,43 @@ export class UserController extends createApiController(
356
422
  security: {
357
423
  getAll: 'public', // No auth required
358
424
  getById: 'jwt', // JWT required
359
- insert: { level: 'permission', permissions: ['users.create'] },
360
- update: { level: 'permission', permissions: ['users.update'] },
361
- delete: { level: 'permission', permissions: ['users.delete'] },
425
+ insert: { level: 'permission', permissions: ['user.create'] },
426
+ update: { level: 'permission', permissions: ['user.update'] },
427
+ delete: { level: 'permission', permissions: ['user.delete'] },
362
428
  },
363
429
  },
364
430
  ) {}
431
+
432
+ // Permission with OR logic
433
+ {
434
+ security: {
435
+ getAll: { level: 'permission', permissions: ['user.read', 'admin'], operator: 'OR' },
436
+ },
437
+ }
438
+
439
+ // Complex permission logic
440
+ {
441
+ security: {
442
+ delete: {
443
+ level: 'permission',
444
+ logic: {
445
+ type: 'group',
446
+ operator: 'AND',
447
+ children: [
448
+ { type: 'action', actionId: 'user.delete' },
449
+ { type: 'group', operator: 'OR', children: [
450
+ { type: 'action', actionId: 'admin' },
451
+ { type: 'action', actionId: 'manager' },
452
+ ]},
453
+ ],
454
+ },
455
+ },
456
+ },
457
+ }
365
458
  ```
366
459
 
460
+ **Important:** When per-endpoint security is specified, unconfigured endpoints default to `'jwt'` (not `'public'`) for security.
461
+
367
462
  ---
368
463
 
369
464
  ## Decorators
@@ -373,8 +468,8 @@ export class UserController extends createApiController(
373
468
  Extract the logged-in user from request:
374
469
 
375
470
  ```typescript
376
- import { CurrentUser } from '@flusys/nestjs-shared/decorators';
377
- import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
471
+ import { CurrentUser } from '@flusys/nestjs-shared';
472
+ import { ILoggedUserInfo } from '@flusys/nestjs-shared';
378
473
 
379
474
  @Controller('profile')
380
475
  export class ProfileController {
@@ -396,7 +491,7 @@ export class ProfileController {
396
491
  Mark route as public (skip authentication). **Use sparingly - security risk**.
397
492
 
398
493
  ```typescript
399
- import { Public } from '@flusys/nestjs-shared/decorators';
494
+ import { Public } from '@flusys/nestjs-shared';
400
495
 
401
496
  @Controller('auth')
402
497
  export class AuthController {
@@ -411,7 +506,7 @@ export class AuthController {
411
506
  Require specific permission(s) - **AND logic** by default:
412
507
 
413
508
  ```typescript
414
- import { RequirePermission } from '@flusys/nestjs-shared/decorators';
509
+ import { RequirePermission } from '@flusys/nestjs-shared';
415
510
 
416
511
  @Controller('admin')
417
512
  export class AdminController {
@@ -432,7 +527,7 @@ export class AdminController {
432
527
  Require any of the listed permissions - **OR logic**:
433
528
 
434
529
  ```typescript
435
- import { RequireAnyPermission } from '@flusys/nestjs-shared/decorators';
530
+ import { RequireAnyPermission } from '@flusys/nestjs-shared';
436
531
 
437
532
  @Controller('reports')
438
533
  export class ReportsController {
@@ -443,22 +538,26 @@ export class ReportsController {
443
538
  }
444
539
  ```
445
540
 
446
- ### @RequirePermissionCondition
541
+ ### @RequirePermissionLogic
447
542
 
448
- Build complex permission conditions with nested AND/OR logic:
543
+ Build complex permission logic with ILogicNode tree (matches frontend ILogicNode):
449
544
 
450
545
  ```typescript
451
- import { RequirePermissionCondition } from '@flusys/nestjs-shared/decorators';
546
+ import { RequirePermissionLogic } from '@flusys/nestjs-shared';
452
547
 
453
548
  @Controller('sensitive')
454
549
  export class SensitiveController {
455
550
  // Complex: User needs 'users.read' AND ('admin' OR 'manager')
456
- @RequirePermissionCondition({
457
- operator: 'and',
458
- permissions: ['users.read'],
551
+ @RequirePermissionLogic({
552
+ type: 'group',
553
+ operator: 'AND',
459
554
  children: [
460
- { operator: 'or', permissions: ['admin', 'manager'] }
461
- ]
555
+ { type: 'action', actionId: 'users.read' },
556
+ { type: 'group', operator: 'OR', children: [
557
+ { type: 'action', actionId: 'admin' },
558
+ { type: 'action', actionId: 'manager' },
559
+ ]},
560
+ ],
462
561
  })
463
562
  @Post('complex')
464
563
  getComplexData() { }
@@ -470,7 +569,7 @@ export class SensitiveController {
470
569
  Escape HTML entities for XSS prevention:
471
570
 
472
571
  ```typescript
473
- import { SanitizeHtml, SanitizeAndTrim } from '@flusys/nestjs-shared/decorators';
572
+ import { SanitizeHtml, SanitizeAndTrim } from '@flusys/nestjs-shared';
474
573
 
475
574
  export class CreateCommentDto {
476
575
  @SanitizeHtml()
@@ -488,7 +587,7 @@ export class CreateCommentDto {
488
587
  Generates Swagger schema for response:
489
588
 
490
589
  ```typescript
491
- import { ApiResponseDto } from '@flusys/nestjs-shared/decorators';
590
+ import { ApiResponseDto } from '@flusys/nestjs-shared';
492
591
 
493
592
  @Controller('users')
494
593
  export class UserController {
@@ -511,29 +610,30 @@ export class UserController {
511
610
  Validates JWT tokens for protected routes. Extends Passport's `AuthGuard('jwt')` and respects `@Public()` decorator.
512
611
 
513
612
  ```typescript
514
- import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
613
+ import { JwtAuthGuard } from '@flusys/nestjs-shared';
614
+ import { APP_GUARD } from '@nestjs/core';
515
615
 
516
- // Apply globally in main.ts
616
+ // Apply globally
517
617
  @Module({
518
618
  providers: [{ provide: APP_GUARD, useClass: JwtAuthGuard }],
519
619
  })
520
620
  export class AppModule {}
521
621
  ```
522
622
 
523
- **Important:** Constructor needs `@Inject(Reflector)` for bundled code.
623
+ **Important:** Constructor needs `@Inject(Reflector)` for bundled code (this is already handled in the guard).
524
624
 
525
625
  ### PermissionGuard
526
626
 
527
- Checks user permissions from cache with AND/OR/nested logic support.
627
+ Checks user permissions from cache with AND/OR/nested logic support and wildcard matching.
528
628
 
529
629
  ```typescript
530
- import { PermissionGuard } from '@flusys/nestjs-shared/guards';
630
+ import { PermissionGuard, PERMISSION_GUARD_CONFIG, CACHE_INSTANCE } from '@flusys/nestjs-shared';
531
631
 
532
632
  @Module({
533
633
  providers: [
534
634
  { provide: APP_GUARD, useClass: PermissionGuard },
535
635
  {
536
- provide: 'PERMISSION_GUARD_CONFIG',
636
+ provide: PERMISSION_GUARD_CONFIG,
537
637
  useValue: {
538
638
  enableCompanyFeature: true,
539
639
  userPermissionKeyFormat: 'permissions:user:{userId}',
@@ -545,24 +645,85 @@ import { PermissionGuard } from '@flusys/nestjs-shared/guards';
545
645
  export class AppModule {}
546
646
  ```
547
647
 
648
+ **PermissionGuardConfig Interface:**
649
+
650
+ ```typescript
651
+ interface PermissionGuardConfig {
652
+ enableCompanyFeature?: boolean;
653
+ userPermissionKeyFormat?: string; // Default: 'permissions:user:{userId}'
654
+ companyPermissionKeyFormat?: string; // Default: 'permissions:company:{companyId}:branch:{branchId}:user:{userId}'
655
+ }
656
+ ```
657
+
548
658
  **Cache Key Formats:**
549
659
 
550
660
  ```typescript
551
- // Without company feature
552
- `permissions:user:{userId}`
661
+ // Without company feature (enableCompanyFeature: false)
662
+ 'permissions:user:{userId}'
663
+
664
+ // With company feature (enableCompanyFeature: true && user.companyId exists)
665
+ 'permissions:company:{companyId}:branch:{branchId}:user:{userId}'
666
+ ```
667
+
668
+ **Wildcard Permission Matching:**
669
+
670
+ The guard supports wildcard permissions for flexible access control:
671
+
672
+ ```typescript
673
+ // User has permissions: ['user.*', 'admin']
674
+
675
+ // These checks will PASS:
676
+ @RequirePermission('user.create') // Matches 'user.*'
677
+ @RequirePermission('user.read') // Matches 'user.*'
678
+ @RequirePermission('admin') // Exact match
553
679
 
554
- // With company feature
555
- `permissions:company:{companyId}:branch:{branchId}:user:{userId}`
680
+ // Wildcard patterns:
681
+ '*' // Matches ALL permissions (super admin)
682
+ 'user.*' // Matches 'user.create', 'user.read', 'user.update', etc.
683
+ 'storage.*' // Matches 'storage.file.create', 'storage.folder.read', etc.
556
684
  ```
557
685
 
558
686
  ### Permission Exceptions
559
687
 
560
688
  ```typescript
561
689
  import {
562
- InsufficientPermissionsException, // 403 - Missing permissions
563
- NoPermissionsFoundException, // 403 - No permissions in cache
564
- PermissionSystemUnavailableException, // 500 - Cache unavailable
565
- } from '@flusys/nestjs-shared/exceptions';
690
+ InsufficientPermissionsException, // 403 - Missing required permissions
691
+ NoPermissionsFoundException, // 403 - No permissions in cache for user
692
+ PermissionSystemUnavailableException, // 500 - Cache not available
693
+ } from '@flusys/nestjs-shared';
694
+
695
+ // Exception response formats:
696
+ // InsufficientPermissionsException (AND):
697
+ {
698
+ "success": false,
699
+ "message": "Missing required permissions: user.create, user.update",
700
+ "code": "INSUFFICIENT_PERMISSIONS",
701
+ "missingPermissions": ["user.create", "user.update"],
702
+ "operator": "AND"
703
+ }
704
+
705
+ // InsufficientPermissionsException (OR):
706
+ {
707
+ "success": false,
708
+ "message": "Requires at least one of: admin, manager",
709
+ "code": "INSUFFICIENT_PERMISSIONS",
710
+ "missingPermissions": ["admin", "manager"],
711
+ "operator": "OR"
712
+ }
713
+
714
+ // NoPermissionsFoundException:
715
+ {
716
+ "success": false,
717
+ "message": "No permissions found. Please contact administrator.",
718
+ "code": "NO_PERMISSIONS_FOUND"
719
+ }
720
+
721
+ // PermissionSystemUnavailableException:
722
+ {
723
+ "success": false,
724
+ "message": "Permission system temporarily unavailable. Please try again later.",
725
+ "code": "PERMISSION_SYSTEM_UNAVAILABLE"
726
+ }
566
727
  ```
567
728
 
568
729
  ---
@@ -571,20 +732,24 @@ import {
571
732
 
572
733
  ### LoggerMiddleware
573
734
 
574
- Combined middleware for request correlation and HTTP logging.
735
+ Combined middleware for request correlation and HTTP logging using AsyncLocalStorage.
575
736
 
576
737
  **Features:**
577
738
  - Request ID generation/tracking (UUID or from `x-request-id` header)
578
739
  - Tenant ID tracking (from `x-tenant-id` header)
579
- - AsyncLocalStorage context for thread-safe access
740
+ - AsyncLocalStorage context for thread-safe access across async operations
580
741
  - Automatic sensitive header redaction (authorization, cookie, x-api-key)
581
742
  - Performance monitoring (warns on requests > 3s)
582
- - Body truncation (max 1000 chars)
743
+ - Body truncation (max 1000 chars in logs)
744
+ - Excluded paths: `/health`, `/metrics`, `/favicon.ico`
745
+
746
+ **Configuration via environment:**
747
+ - `LOG_LEVEL` - Logging level (debug enables verbose logging)
583
748
 
584
749
  **Usage:**
585
750
 
586
751
  ```typescript
587
- import { LoggerMiddleware } from '@flusys/nestjs-shared/middlewares';
752
+ import { LoggerMiddleware } from '@flusys/nestjs-shared';
588
753
 
589
754
  @Module({})
590
755
  export class AppModule implements NestModule {
@@ -594,7 +759,19 @@ export class AppModule implements NestModule {
594
759
  }
595
760
  ```
596
761
 
597
- **Accessing Request Context:**
762
+ **Request Context Interface:**
763
+
764
+ ```typescript
765
+ interface IRequestContext {
766
+ requestId: string;
767
+ tenantId?: string;
768
+ userId?: string;
769
+ companyId?: string;
770
+ startTime: number;
771
+ }
772
+ ```
773
+
774
+ **Accessing Request Context (read-only):**
598
775
 
599
776
  ```typescript
600
777
  import {
@@ -602,34 +779,37 @@ import {
602
779
  getTenantId,
603
780
  getUserId,
604
781
  getCompanyId,
605
- setUserId,
606
- setCompanyId,
607
- } from '@flusys/nestjs-shared/middlewares';
782
+ requestContext, // AsyncLocalStorage instance for advanced usage
783
+ } from '@flusys/nestjs-shared';
608
784
 
609
785
  @Injectable()
610
786
  export class MyService {
611
787
  async doSomething() {
612
- const requestId = getRequestId();
613
- const tenantId = getTenantId();
614
- setUserId('user-123');
788
+ const requestId = getRequestId(); // Current request's correlation ID
789
+ const tenantId = getTenantId(); // From x-tenant-id header
790
+ const userId = getUserId(); // Set by auth guard (if available)
791
+ const companyId = getCompanyId(); // Set by auth (if available)
615
792
  }
616
793
  }
617
794
  ```
618
795
 
796
+ **Note:** The context values (`userId`, `companyId`) are populated by authentication guards after JWT validation. The middleware only initializes `requestId` and `tenantId` from headers.
797
+
619
798
  ---
620
799
 
621
800
  ## Interceptors
622
801
 
623
802
  ### ResponseMetaInterceptor
624
803
 
625
- Adds `_meta` to all responses:
804
+ Automatically adds `_meta` to all responses with `success` field:
626
805
 
627
806
  ```typescript
628
- // Response includes:
807
+ // Response structure:
629
808
  {
809
+ "success": true,
630
810
  "data": [...],
631
811
  "_meta": {
632
- "requestId": "abc-123",
812
+ "requestId": "req_abc123def456",
633
813
  "timestamp": "2024-01-01T00:00:00.000Z",
634
814
  "responseTime": 45
635
815
  }
@@ -638,23 +818,92 @@ Adds `_meta` to all responses:
638
818
 
639
819
  ### IdempotencyInterceptor
640
820
 
641
- Prevents duplicate POST requests using `X-Idempotency-Key` header. Caches responses for 24 hours.
821
+ Prevents duplicate POST requests using `X-Idempotency-Key` header.
822
+
823
+ **How it works:**
824
+ 1. If key exists and completed → returns cached response (no reprocessing)
825
+ 2. If key exists but processing → throws `ConflictException` (409)
826
+ 3. If key is new → processes request and caches response for 24 hours
827
+
828
+ **Usage:**
829
+
830
+ ```typescript
831
+ // Client includes header:
832
+ // X-Idempotency-Key: unique-order-123
833
+
834
+ // Controller (auto-applied by createApiController for insert endpoints)
835
+ @UseInterceptors(IdempotencyInterceptor)
836
+ @Post('create-order')
837
+ createOrder(@Body() dto: CreateOrderDto) { }
838
+ ```
642
839
 
643
840
  ### SetCreatedByOnBody / SetUpdateByOnBody / SetDeletedByOnBody
644
841
 
645
842
  Auto-set audit user IDs on request body from authenticated user.
646
843
 
844
+ ```typescript
845
+ // Factory function for custom field names
846
+ import { createSetUserFieldInterceptor } from '@flusys/nestjs-shared';
847
+
848
+ // Built-in interceptors:
849
+ export const SetCreatedByOnBody = createSetUserFieldInterceptor('createdById');
850
+ export const SetUpdateByOnBody = createSetUserFieldInterceptor('updatedById');
851
+ export const SetDeletedByOnBody = createSetUserFieldInterceptor('deletedById');
852
+
853
+ // Usage:
854
+ @UseInterceptors(SetCreatedByOnBody)
855
+ @Post('insert')
856
+ insert(@Body() dto: CreateDto) { }
857
+
858
+ // Works with arrays too:
859
+ // Input: [{ name: 'A' }, { name: 'B' }]
860
+ // Output: [{ name: 'A', createdById: 'user-123' }, { name: 'B', createdById: 'user-123' }]
861
+ ```
862
+
647
863
  ### DeleteEmptyIdFromBodyInterceptor
648
864
 
649
- Removes empty `id` fields from request body (single and array bodies).
865
+ Removes empty/null `id` fields from request body (single objects and arrays).
866
+
867
+ ```typescript
868
+ // Input: { id: '', name: 'Test' }
869
+ // Output: { name: 'Test' }
870
+
871
+ // Input: [{ id: null, name: 'A' }, { id: '123', name: 'B' }]
872
+ // Output: [{ name: 'A' }, { id: '123', name: 'B' }]
873
+ ```
650
874
 
651
875
  ### QueryPerformanceInterceptor
652
876
 
653
- Monitors execution time and warns if request > 1000ms (configurable).
877
+ Monitors endpoint execution time and logs warnings for slow requests.
878
+
879
+ ```typescript
880
+ import { QueryPerformanceInterceptor } from '@flusys/nestjs-shared';
881
+
882
+ // Default threshold: 1000ms
883
+ app.useGlobalInterceptors(new QueryPerformanceInterceptor());
884
+
885
+ // Custom threshold: 500ms
886
+ app.useGlobalInterceptors(new QueryPerformanceInterceptor(500));
887
+ ```
654
888
 
655
889
  ### Slug Interceptor
656
890
 
657
- Auto-generates `slug` from `name` field using `UtilsService.transformToSlug()`.
891
+ Auto-generates `slug` from `name` field if not provided.
892
+
893
+ ```typescript
894
+ import { Slug } from '@flusys/nestjs-shared';
895
+
896
+ @UseInterceptors(Slug)
897
+ @Post('insert')
898
+ insert(@Body() dto: CreateDto) { }
899
+
900
+ // Input: { name: 'My Product Name' }
901
+ // Output: { name: 'My Product Name', slug: 'my-product-name' }
902
+
903
+ // If slug already provided, it's preserved:
904
+ // Input: { name: 'My Product', slug: 'custom-slug' }
905
+ // Output: { name: 'My Product', slug: 'custom-slug' }
906
+ ```
658
907
 
659
908
  ---
660
909
 
@@ -665,7 +914,7 @@ Auto-generates `slug` from `name` field using `UtilsService.transformToSlug()`.
665
914
  Two-tier caching with in-memory (L1) and Redis (L2):
666
915
 
667
916
  ```typescript
668
- import { HybridCache } from '@flusys/nestjs-shared/classes';
917
+ import { HybridCache } from '@flusys/nestjs-shared';
669
918
 
670
919
  @Injectable()
671
920
  export class MyService {
@@ -695,7 +944,7 @@ export class MyService {
695
944
  ### CacheModule Setup
696
945
 
697
946
  ```typescript
698
- import { CacheModule } from '@flusys/nestjs-shared/modules';
947
+ import { CacheModule } from '@flusys/nestjs-shared';
699
948
 
700
949
  @Module({
701
950
  imports: [
@@ -723,7 +972,7 @@ Dynamic database connection management with connection pooling.
723
972
  ### Setup
724
973
 
725
974
  ```typescript
726
- import { DataSourceModule } from '@flusys/nestjs-shared/modules';
975
+ import { DataSourceModule } from '@flusys/nestjs-shared';
727
976
 
728
977
  @Module({
729
978
  imports: [
@@ -766,7 +1015,7 @@ export class AppModule {}
766
1015
  ### FilterAndPaginationDto
767
1016
 
768
1017
  ```typescript
769
- import { FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
1018
+ import { FilterAndPaginationDto } from '@flusys/nestjs-shared';
770
1019
 
771
1020
  // Request body
772
1021
  {
@@ -781,7 +1030,7 @@ import { FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
781
1030
  ### DeleteDto
782
1031
 
783
1032
  ```typescript
784
- import { DeleteDto } from '@flusys/nestjs-shared/dtos';
1033
+ import { DeleteDto } from '@flusys/nestjs-shared';
785
1034
 
786
1035
  // Soft delete
787
1036
  { "id": "uuid", "type": "delete" }
@@ -842,7 +1091,7 @@ class MessageResponseDto {
842
1091
  Base entity with UUID and timestamps:
843
1092
 
844
1093
  ```typescript
845
- import { Identity } from '@flusys/nestjs-shared/entities';
1094
+ import { Identity } from '@flusys/nestjs-shared';
846
1095
 
847
1096
  @Entity()
848
1097
  export class Product extends Identity {
@@ -859,7 +1108,7 @@ export class Product extends Identity {
859
1108
  Base user entity with common fields:
860
1109
 
861
1110
  ```typescript
862
- import { UserRoot } from '@flusys/nestjs-shared/entities';
1111
+ import { UserRoot } from '@flusys/nestjs-shared';
863
1112
 
864
1113
  @Entity()
865
1114
  export class User extends UserRoot {
@@ -882,7 +1131,7 @@ import {
882
1131
  buildCompanyWhereCondition,
883
1132
  hasCompanyId,
884
1133
  validateCompanyOwnership,
885
- } from '@flusys/nestjs-shared/utils';
1134
+ } from '@flusys/nestjs-shared';
886
1135
 
887
1136
  // Add company filter to TypeORM query
888
1137
  applyCompanyFilter(query, {
@@ -900,7 +1149,7 @@ validateCompanyOwnership(entity, user, 'Entity');
900
1149
  ### String Utilities
901
1150
 
902
1151
  ```typescript
903
- import { generateSlug, generateUniqueSlug } from '@flusys/nestjs-shared/utils';
1152
+ import { generateSlug, generateUniqueSlug } from '@flusys/nestjs-shared';
904
1153
 
905
1154
  // Generate URL-friendly slug
906
1155
  const slug = generateSlug('My Product Name', 100);
@@ -921,7 +1170,7 @@ import {
921
1170
  isBrowserRequest,
922
1171
  buildCookieOptions,
923
1172
  parseDurationToMs,
924
- } from '@flusys/nestjs-shared/utils';
1173
+ } from '@flusys/nestjs-shared';
925
1174
 
926
1175
  // Detect browser vs API client
927
1176
  const isBrowser = isBrowserRequest(req);
@@ -936,7 +1185,7 @@ const ms = parseDurationToMs('7d'); // 604800000
936
1185
  ### HTML Sanitizer
937
1186
 
938
1187
  ```typescript
939
- import { escapeHtml, escapeHtmlVariables } from '@flusys/nestjs-shared/utils';
1188
+ import { escapeHtml, escapeHtmlVariables } from '@flusys/nestjs-shared';
940
1189
 
941
1190
  // Escape single string
942
1191
  const safe = escapeHtml('<script>alert("xss")</script>');
@@ -953,68 +1202,228 @@ const safeVars = escapeHtmlVariables({
953
1202
 
954
1203
  ## Error Handling
955
1204
 
956
- ### Error Handler Utilities
1205
+ ### ErrorHandler Class
1206
+
1207
+ Centralized error handling with automatic sensitive data redaction.
957
1208
 
958
1209
  ```typescript
959
- import {
960
- getErrorMessage,
961
- logError,
962
- rethrowError,
963
- logAndRethrow,
964
- } from '@flusys/nestjs-shared/utils';
1210
+ import { ErrorHandler, IErrorContext } from '@flusys/nestjs-shared';
1211
+ import { Logger } from '@nestjs/common';
965
1212
 
1213
+ const logger = new Logger('MyService');
1214
+
1215
+ // Error context interface
966
1216
  interface IErrorContext {
967
1217
  operation?: string;
968
1218
  entity?: string;
969
1219
  userId?: string;
970
1220
  id?: string;
971
1221
  companyId?: string;
1222
+ branchId?: string;
1223
+ sectionId?: string;
972
1224
  data?: Record<string, unknown>;
973
1225
  }
974
1226
 
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 });
1227
+ // Safe error message extraction from unknown error
1228
+ const message = ErrorHandler.getErrorMessage(error);
1229
+ // Returns: 'Error message' | 'Unknown error occurred'
980
1230
 
981
- // Type-safe rethrow
982
- rethrowError(error);
1231
+ // Log error with sensitive key redaction
1232
+ // Automatically redacts: password, secret, token, apiKey, credential, authorization
1233
+ ErrorHandler.logError(logger, error, 'createUser', {
1234
+ entity: 'User',
1235
+ userId: user.id,
1236
+ data: { email: 'test@example.com', password: 'secret123' }, // password will be [REDACTED]
1237
+ });
983
1238
 
984
- // Combined log + rethrow
985
- logAndRethrow(logger, error, 'updateUser', context);
1239
+ // Type-safe rethrow (preserves Error instances, wraps others)
1240
+ ErrorHandler.rethrowError(error); // throws: never
1241
+
1242
+ // Combined log + rethrow (common pattern)
1243
+ ErrorHandler.logAndRethrow(logger, error, 'updateUser', { entity: 'User', id: userId });
1244
+
1245
+ // Typical usage in service
1246
+ async createUser(dto: CreateUserDto): Promise<User> {
1247
+ try {
1248
+ return await this.repository.save(dto);
1249
+ } catch (error) {
1250
+ ErrorHandler.logAndRethrow(this.logger, error, 'createUser', {
1251
+ entity: 'User',
1252
+ data: dto,
1253
+ });
1254
+ }
1255
+ }
986
1256
  ```
987
1257
 
1258
+ **Sensitive Keys (automatically redacted in logs):**
1259
+ - `password`
1260
+ - `secret`
1261
+ - `token`
1262
+ - `apiKey`
1263
+ - `credential`
1264
+ - `authorization`
1265
+
988
1266
  ---
989
1267
 
990
1268
  ## Constants
991
1269
 
1270
+ ### Metadata Keys
1271
+
1272
+ ```typescript
1273
+ import { IS_PUBLIC_KEY, PERMISSIONS_KEY } from '@flusys/nestjs-shared';
1274
+
1275
+ // Used internally by decorators:
1276
+ IS_PUBLIC_KEY // 'isPublic' - marks routes as public
1277
+ PERMISSIONS_KEY // 'permissions' - stores permission requirements
1278
+ ```
1279
+
1280
+ ### Injection Tokens
1281
+
1282
+ ```typescript
1283
+ import {
1284
+ CACHE_INSTANCE,
1285
+ PERMISSION_GUARD_CONFIG,
1286
+ LOGGER_INSTANCE,
1287
+ } from '@flusys/nestjs-shared';
1288
+
1289
+ // Injection tokens:
1290
+ CACHE_INSTANCE // 'CACHE_INSTANCE' - HybridCache provider
1291
+ PERMISSION_GUARD_CONFIG // 'PERMISSION_GUARD_CONFIG' - PermissionGuard config
1292
+ LOGGER_INSTANCE // 'LOGGER_INSTANCE' - ILogger provider
1293
+ ```
1294
+
1295
+ ### Header Names
1296
+
1297
+ ```typescript
1298
+ import {
1299
+ IDEMPOTENCY_KEY_HEADER,
1300
+ REQUEST_ID_HEADER,
1301
+ CLIENT_TYPE_HEADER,
1302
+ } from '@flusys/nestjs-shared';
1303
+
1304
+ // HTTP header names:
1305
+ IDEMPOTENCY_KEY_HEADER // 'x-idempotency-key' - For IdempotencyInterceptor
1306
+ REQUEST_ID_HEADER // 'x-request-id' - For request correlation
1307
+ CLIENT_TYPE_HEADER // 'x-client-type' - For client detection (browser/api)
1308
+ ```
1309
+
1310
+ ### Cache Key Prefixes
1311
+
1312
+ ```typescript
1313
+ import {
1314
+ PERMISSIONS_CACHE_PREFIX,
1315
+ IDEMPOTENCY_CACHE_PREFIX,
1316
+ } from '@flusys/nestjs-shared';
1317
+
1318
+ // Cache key prefixes:
1319
+ PERMISSIONS_CACHE_PREFIX // 'permissions' - For user permissions cache
1320
+ IDEMPOTENCY_CACHE_PREFIX // 'idempotency' - For idempotency keys
1321
+ ```
1322
+
992
1323
  ### Permission Constants
993
1324
 
994
1325
  ```typescript
995
- import { PERMISSIONS } from '@flusys/nestjs-shared/constants';
1326
+ import { PERMISSIONS, PermissionCode } from '@flusys/nestjs-shared';
996
1327
 
997
- // Organized by module:
1328
+ // Auth Module
998
1329
  PERMISSIONS.USER.CREATE // 'user.create'
999
1330
  PERMISSIONS.USER.READ // 'user.read'
1000
1331
  PERMISSIONS.USER.UPDATE // 'user.update'
1001
1332
  PERMISSIONS.USER.DELETE // 'user.delete'
1002
1333
 
1003
1334
  PERMISSIONS.COMPANY.CREATE // 'company.create'
1335
+ PERMISSIONS.COMPANY.READ // 'company.read'
1336
+ // ...
1337
+
1004
1338
  PERMISSIONS.BRANCH.CREATE // 'branch.create'
1339
+ // ...
1005
1340
 
1341
+ // IAM Module
1342
+ PERMISSIONS.ACTION.CREATE // 'action.create'
1006
1343
  PERMISSIONS.ROLE.CREATE // 'role.create'
1344
+ PERMISSIONS.ROLE_ACTION.READ // 'role-action.read'
1345
+ PERMISSIONS.ROLE_ACTION.ASSIGN // 'role-action.assign'
1346
+ PERMISSIONS.USER_ROLE.READ // 'user-role.read'
1347
+ PERMISSIONS.USER_ROLE.ASSIGN // 'user-role.assign'
1348
+ PERMISSIONS.USER_ACTION.READ // 'user-action.read'
1349
+ PERMISSIONS.USER_ACTION.ASSIGN // 'user-action.assign'
1350
+ PERMISSIONS.COMPANY_ACTION.READ // 'company-action.read'
1351
+ PERMISSIONS.COMPANY_ACTION.ASSIGN // 'company-action.assign'
1352
+
1353
+ // Storage Module
1007
1354
  PERMISSIONS.FILE.CREATE // 'file.create'
1008
1355
  PERMISSIONS.FOLDER.CREATE // 'folder.create'
1009
- // ...etc
1356
+ PERMISSIONS.STORAGE_CONFIG.CREATE // 'storage-config.create'
1357
+
1358
+ // Email Module
1359
+ PERMISSIONS.EMAIL_CONFIG.CREATE // 'email-config.create'
1360
+ PERMISSIONS.EMAIL_TEMPLATE.CREATE // 'email-template.create'
1361
+
1362
+ // Form Builder Module
1363
+ PERMISSIONS.FORM.CREATE // 'form.create'
1364
+ PERMISSIONS.FORM_RESULT.CREATE // 'form-result.create'
1365
+
1366
+ // Type-safe permission code type:
1367
+ type PermissionCode = 'user.create' | 'user.read' | ... ; // Union of all permission strings
1010
1368
  ```
1011
1369
 
1012
- ### Injection Tokens
1370
+ ---
1371
+
1372
+ ## Logger System
1373
+
1374
+ ### Winston Logger
1375
+
1376
+ Production-ready logging with tenant-aware routing and daily rotation.
1377
+
1378
+ **Configuration via environment:**
1379
+ - `LOG_DIR` - Directory for log files (default: `logs/`)
1380
+ - `LOG_LEVEL` - Minimum log level (default: `info`)
1381
+ - `LOG_MAX_SIZE` - Max file size before rotation
1382
+ - `LOG_MAX_FILES` - Max number of log files to keep
1383
+ - `USE_TENANT_MODE` - Enable tenant-aware log routing
1384
+
1385
+ **Logger Modes:**
1386
+
1387
+ ```typescript
1388
+ import { instance as winstonLogger } from '@flusys/nestjs-shared';
1389
+
1390
+ // Development: Console output with colors
1391
+ // Production: File-based with daily rotation + tenant routing
1392
+ ```
1393
+
1394
+ **Log File Structure:**
1395
+
1396
+ ```
1397
+ logs/
1398
+ ├── combined-2024-01-15.log # All logs (tenant mode off)
1399
+ ├── error-2024-01-15.log # Error logs only
1400
+ └── {tenantId}/ # Tenant-specific folders (tenant mode on)
1401
+ └── combined-2024-01-15.log
1402
+ ```
1403
+
1404
+ ### Logger Adapters
1013
1405
 
1014
1406
  ```typescript
1015
- 'CACHE_INSTANCE' // HybridCache provider
1016
- 'PERMISSION_GUARD_CONFIG' // PermissionGuard config
1017
- 'LOGGER_INSTANCE' // Logger provider
1407
+ import { WinstonLoggerAdapter, NestLoggerAdapter, ILogger } from '@flusys/nestjs-shared';
1408
+ import { Logger } from '@nestjs/common';
1409
+
1410
+ // ILogger interface
1411
+ interface ILogger {
1412
+ log(message: string, context?: string, ...args: any[]): void;
1413
+ error(message: string, trace?: string, context?: string, ...args: any[]): void;
1414
+ warn(message: string, context?: string, ...args: any[]): void;
1415
+ debug(message: string, context?: string, ...args: any[]): void;
1416
+ verbose(message: string, context?: string, ...args: any[]): void;
1417
+ }
1418
+
1419
+ // Winston adapter (recommended for production)
1420
+ // Automatically includes correlation context (requestId, userId, tenantId, companyId)
1421
+ const logger = new WinstonLoggerAdapter('MyService');
1422
+ logger.log('Operation completed', undefined, { orderId: '123' });
1423
+ // Output: { requestId: 'uuid', userId: 'user-id', context: 'MyService', message: 'Operation completed', orderId: '123' }
1424
+
1425
+ // NestJS adapter (for testing or simple use cases)
1426
+ const nestLogger = new NestLoggerAdapter(new Logger('MyService'));
1018
1427
  ```
1019
1428
 
1020
1429
  ---
@@ -1023,59 +1432,69 @@ PERMISSIONS.FOLDER.CREATE // 'folder.create'
1023
1432
 
1024
1433
  ### Main Exports
1025
1434
 
1435
+ All exports are available from the main package entry point:
1436
+
1026
1437
  ```typescript
1027
- // Classes
1438
+ // Import everything from main entry
1028
1439
  import {
1440
+ // Classes
1029
1441
  ApiService,
1030
1442
  RequestScopedApiService,
1031
1443
  createApiController,
1032
1444
  HybridCache,
1033
1445
  WinstonLoggerAdapter,
1034
1446
  NestLoggerAdapter,
1035
- } from '@flusys/nestjs-shared/classes';
1447
+ instance as winstonLogger,
1036
1448
 
1037
- // Decorators
1038
- import {
1449
+ // Controller Types
1450
+ ApiEndpoint,
1451
+ SecurityLevel,
1452
+ EndpointSecurity,
1453
+ ApiSecurityConfig,
1454
+ ApiControllerOptions,
1455
+
1456
+ // Decorators
1039
1457
  CurrentUser,
1040
1458
  Public,
1041
1459
  RequirePermission,
1042
1460
  RequireAnyPermission,
1043
- RequirePermissionCondition,
1461
+ RequirePermissionLogic,
1462
+ RequirePermissionCondition, // @deprecated - use RequirePermissionLogic
1044
1463
  SanitizeHtml,
1045
1464
  SanitizeAndTrim,
1046
1465
  ApiResponseDto,
1047
- } from '@flusys/nestjs-shared/decorators';
1466
+ ArrayResponseType,
1048
1467
 
1049
- // Guards
1050
- import { JwtAuthGuard, PermissionGuard } from '@flusys/nestjs-shared/guards';
1468
+ // Guards
1469
+ JwtAuthGuard,
1470
+ PermissionGuard,
1051
1471
 
1052
- // Interceptors
1053
- import {
1472
+ // Interceptors
1054
1473
  ResponseMetaInterceptor,
1055
1474
  IdempotencyInterceptor,
1056
1475
  SetCreatedByOnBody,
1057
1476
  SetUpdateByOnBody,
1058
1477
  SetDeletedByOnBody,
1478
+ createSetUserFieldInterceptor, // Factory function
1059
1479
  DeleteEmptyIdFromBodyInterceptor,
1060
1480
  QueryPerformanceInterceptor,
1061
1481
  Slug,
1062
- } from '@flusys/nestjs-shared/interceptors';
1063
1482
 
1064
- // Modules
1065
- import {
1483
+ // Modules
1066
1484
  CacheModule,
1067
1485
  DataSourceModule,
1486
+ DataSourceModuleOptions,
1487
+ DataSourceOptionsFactory,
1488
+ DataSourceModuleAsyncOptions,
1068
1489
  UtilsModule,
1069
1490
  UtilsService,
1070
1491
  MultiTenantDataSourceService,
1071
- } from '@flusys/nestjs-shared/modules';
1072
1492
 
1073
- // DTOs
1074
- import {
1493
+ // DTOs
1075
1494
  FilterAndPaginationDto,
1495
+ GetByIdBodyDto,
1076
1496
  PaginationDto,
1077
1497
  DeleteDto,
1078
- GetByIdBodyDto,
1079
1498
  SingleResponseDto,
1080
1499
  ListResponseDto,
1081
1500
  BulkResponseDto,
@@ -1084,58 +1503,137 @@ import {
1084
1503
  PaginationMetaDto,
1085
1504
  BulkMetaDto,
1086
1505
  RequestMetaDto,
1087
- } from '@flusys/nestjs-shared/dtos';
1088
1506
 
1089
- // Entities
1090
- import { Identity, UserRoot } from '@flusys/nestjs-shared/entities';
1507
+ // Entities
1508
+ Identity,
1509
+ UserRoot,
1091
1510
 
1092
- // Interfaces
1093
- import {
1511
+ // Interfaces
1094
1512
  ILoggedUserInfo,
1095
1513
  IService,
1096
1514
  IDataSourceProvider,
1097
1515
  IModuleConfigService,
1098
1516
  ILogger,
1099
- PermissionCondition,
1100
- PermissionOperator,
1101
- } from '@flusys/nestjs-shared/interfaces';
1102
-
1103
- // Utilities
1104
- import {
1105
- getErrorMessage,
1106
- logError,
1107
- applyCompanyFilter,
1108
- buildCompanyWhereCondition,
1109
- validateCompanyOwnership,
1110
- generateSlug,
1111
- generateUniqueSlug,
1112
- isBrowserRequest,
1113
- buildCookieOptions,
1114
- parseDurationToMs,
1115
- escapeHtml,
1116
- escapeHtmlVariables,
1117
- } from '@flusys/nestjs-shared/utils';
1118
-
1119
- // Middleware
1120
- import {
1517
+ IIdentity,
1518
+ ILogicNode,
1519
+ IActionNode,
1520
+ IGroupNode,
1521
+ IPermissionLogic,
1522
+ SimplePermissionConfig,
1523
+ PermissionConfig,
1524
+ PermissionGuardConfig,
1525
+
1526
+ // Middleware
1121
1527
  LoggerMiddleware,
1528
+ IRequestContext,
1529
+ requestContext,
1122
1530
  getRequestId,
1123
1531
  getTenantId,
1124
1532
  getUserId,
1125
1533
  getCompanyId,
1126
- setUserId,
1127
- setCompanyId,
1128
- } from '@flusys/nestjs-shared/middlewares';
1129
1534
 
1130
- // Exceptions
1131
- import {
1535
+ // Exceptions
1132
1536
  InsufficientPermissionsException,
1133
1537
  NoPermissionsFoundException,
1134
1538
  PermissionSystemUnavailableException,
1135
- } from '@flusys/nestjs-shared/exceptions';
1136
1539
 
1137
- // Constants
1138
- import { PERMISSIONS } from '@flusys/nestjs-shared/constants';
1540
+ // Constants
1541
+ IS_PUBLIC_KEY,
1542
+ PERMISSIONS_KEY,
1543
+ CACHE_INSTANCE,
1544
+ PERMISSION_GUARD_CONFIG,
1545
+ LOGGER_INSTANCE,
1546
+ IDEMPOTENCY_KEY_HEADER,
1547
+ REQUEST_ID_HEADER,
1548
+ CLIENT_TYPE_HEADER,
1549
+ PERMISSIONS_CACHE_PREFIX,
1550
+ IDEMPOTENCY_CACHE_PREFIX,
1551
+ PERMISSIONS,
1552
+ PermissionCode,
1553
+ // Individual permission exports
1554
+ USER_PERMISSIONS,
1555
+ COMPANY_PERMISSIONS,
1556
+ BRANCH_PERMISSIONS,
1557
+ ACTION_PERMISSIONS,
1558
+ ROLE_PERMISSIONS,
1559
+ ROLE_ACTION_PERMISSIONS,
1560
+ USER_ROLE_PERMISSIONS,
1561
+ USER_ACTION_PERMISSIONS,
1562
+ COMPANY_ACTION_PERMISSIONS,
1563
+ FILE_PERMISSIONS,
1564
+ FOLDER_PERMISSIONS,
1565
+ STORAGE_CONFIG_PERMISSIONS,
1566
+ EMAIL_CONFIG_PERMISSIONS,
1567
+ EMAIL_TEMPLATE_PERMISSIONS,
1568
+ FORM_PERMISSIONS,
1569
+ FORM_RESULT_PERMISSIONS,
1570
+
1571
+ // Utilities
1572
+ ErrorHandler,
1573
+ IErrorContext,
1574
+ escapeHtml,
1575
+ escapeHtmlVariables,
1576
+ applyCompanyFilter,
1577
+ buildCompanyWhereCondition,
1578
+ hasCompanyId,
1579
+ validateCompanyOwnership,
1580
+ ICompanyFilterConfig,
1581
+ ICompanyEnabled,
1582
+ isBrowserRequest,
1583
+ buildCookieOptions,
1584
+ parseDurationToMs,
1585
+ generateSlug,
1586
+ generateUniqueSlug,
1587
+ } from '@flusys/nestjs-shared';
1588
+ ```
1589
+
1590
+ ---
1591
+
1592
+ ## UtilsService
1593
+
1594
+ Global utility service for cache management and string operations.
1595
+
1596
+ ```typescript
1597
+ import { UtilsService, CACHE_INSTANCE, HybridCache } from '@flusys/nestjs-shared';
1598
+
1599
+ @Injectable()
1600
+ export class MyService {
1601
+ constructor(
1602
+ @Inject(UtilsService) private readonly utilsService: UtilsService,
1603
+ @Inject(CACHE_INSTANCE) private readonly cache: HybridCache,
1604
+ ) {}
1605
+
1606
+ // Cache key generation with optional tenant prefix
1607
+ getCacheKey(entityName: string, params: any, entityId?: string, tenantId?: string): string;
1608
+ // Example: this.utilsService.getCacheKey('user', { filter: {} }, 'user-123', 'tenant-1')
1609
+ // Returns: 'tenant_tenant-1_entity_user_id_user-123_select_{"filter":{}}'
1610
+
1611
+ // Track cache keys for invalidation
1612
+ async trackCacheKey(
1613
+ cacheKey: string,
1614
+ entityName: string,
1615
+ cacheManager: HybridCache,
1616
+ entityId?: string,
1617
+ tenantId?: string,
1618
+ ): Promise<void>;
1619
+
1620
+ // Clear all cached entries for an entity
1621
+ async clearCache(
1622
+ entityName: string,
1623
+ cacheManager: HybridCache,
1624
+ entityId?: string,
1625
+ tenantId?: string,
1626
+ ): Promise<void>;
1627
+
1628
+ // Generate URL-friendly slug
1629
+ transformToSlug(value: string, salt?: boolean): string;
1630
+ // Example: this.utilsService.transformToSlug('My Product Name')
1631
+ // Returns: 'my-product-name'
1632
+ // With salt: 'my-product-name-42'
1633
+
1634
+ // Generate random integer
1635
+ getRandomInt(min: number, max: number): number;
1636
+ }
1139
1637
  ```
1140
1638
 
1141
1639
  ---
@@ -1145,58 +1643,136 @@ import { PERMISSIONS } from '@flusys/nestjs-shared/constants';
1145
1643
  ### 1. Use Generic Service Pattern
1146
1644
 
1147
1645
  ```typescript
1148
- // Extend ApiService for consistent CRUD
1646
+ // Extend ApiService for consistent CRUD operations
1149
1647
  @Injectable()
1150
- export class ProductService extends ApiService<...> {
1151
- // Override hooks for customization
1648
+ export class ProductService extends ApiService<
1649
+ CreateProductDto,
1650
+ UpdateProductDto,
1651
+ IProduct,
1652
+ Product,
1653
+ Repository<Product>
1654
+ > {
1655
+ // Override hooks for custom business logic
1656
+ protected async beforeInsertOperation(dto, user, qr): Promise<void> {
1657
+ // Validation, transformations, etc.
1658
+ }
1152
1659
  }
1153
1660
 
1154
- // For dynamic entities, use RequestScopedApiService
1661
+ // For dynamic entity resolution, use RequestScopedApiService
1155
1662
  @Injectable({ scope: Scope.REQUEST })
1156
- export class RoleService extends RequestScopedApiService<...> { }
1663
+ export class RoleService extends RequestScopedApiService<...> {
1664
+ protected resolveEntity() {
1665
+ return this.config.isCompanyFeatureEnabled() ? RoleWithCompany : Role;
1666
+ }
1667
+ }
1157
1668
  ```
1158
1669
 
1159
1670
  ### 2. Always Use @Inject() Decorators
1160
1671
 
1161
- Required for esbuild bundled code:
1672
+ Required for esbuild bundled code (NestJS DI metadata may be lost):
1162
1673
 
1163
1674
  ```typescript
1164
- // CORRECT
1675
+ // CORRECT - explicit injection
1165
1676
  constructor(
1166
1677
  @Inject(MyService) private readonly myService: MyService,
1167
- @Inject('CACHE_INSTANCE') private readonly cache: HybridCache,
1678
+ @Inject(CACHE_INSTANCE) private readonly cache: HybridCache,
1679
+ @Inject(UtilsService) private readonly utils: UtilsService,
1168
1680
  ) {}
1169
1681
 
1170
- // WRONG - fails in bundled code
1682
+ // WRONG - may fail in bundled code
1171
1683
  constructor(private readonly myService: MyService) {}
1172
1684
  ```
1173
1685
 
1174
1686
  ### 3. Use Decorators Consistently
1175
1687
 
1176
1688
  ```typescript
1177
- // Use built-in decorators
1689
+ // Use built-in decorators for type safety
1178
1690
  @CurrentUser() user: ILoggedUserInfo
1179
- @RequirePermission('users.create')
1180
- @Public() // Use sparingly!
1691
+ @CurrentUser('id') userId: string // Extract specific property
1692
+
1693
+ // Permission decorators
1694
+ @RequirePermission('user.create') // Single permission (AND)
1695
+ @RequirePermission('user.create', 'admin') // Multiple permissions (AND)
1696
+ @RequireAnyPermission('user.read', 'admin') // Multiple permissions (OR)
1697
+ @RequirePermissionLogic({ type: 'group', ... }) // Complex logic
1698
+
1699
+ // Mark public routes sparingly - security risk!
1700
+ @Public()
1181
1701
 
1182
- // Don't access request directly
1183
- @Req() req: Request // Not type-safe
1702
+ // Avoid direct request access - use decorators
1703
+ // @Req() req: Request // Not type-safe, avoid!
1184
1704
  ```
1185
1705
 
1186
1706
  ### 4. Configure Security at Controller Level
1187
1707
 
1188
1708
  ```typescript
1189
- // GOOD - configure in createApiController
1190
- export class UserController extends createApiController(..., {
1191
- security: { insert: 'permission', ... },
1192
- }) {}
1709
+ // GOOD - centralized security config
1710
+ export class UserController extends createApiController(
1711
+ CreateUserDto, UpdateUserDto, UserResponseDto,
1712
+ {
1713
+ security: {
1714
+ getAll: 'jwt',
1715
+ insert: { level: 'permission', permissions: ['user.create'] },
1716
+ update: { level: 'permission', permissions: ['user.update'] },
1717
+ delete: { level: 'permission', permissions: ['user.delete'] },
1718
+ },
1719
+ },
1720
+ ) {}
1193
1721
 
1194
- // AVOID - adding guards to each endpoint
1722
+ // AVOID - scattered guards on each endpoint
1195
1723
  @UseGuards(JwtGuard)
1196
1724
  @Post('create')
1197
1725
  create() {}
1198
1726
  ```
1199
1727
 
1728
+ ### 5. Use Permission Constants
1729
+
1730
+ ```typescript
1731
+ import { PERMISSIONS } from '@flusys/nestjs-shared';
1732
+
1733
+ // GOOD - use constants for type safety and refactoring
1734
+ @RequirePermission(PERMISSIONS.USER.CREATE)
1735
+
1736
+ // AVOID - hardcoded strings prone to typos
1737
+ @RequirePermission('user.create')
1738
+ ```
1739
+
1740
+ ### 6. Leverage Company Filtering Utilities
1741
+
1742
+ ```typescript
1743
+ import { applyCompanyFilter, validateCompanyOwnership } from '@flusys/nestjs-shared';
1744
+
1745
+ // In service getExtraManipulateQuery hook
1746
+ protected async getExtraManipulateQuery(query, dto, user) {
1747
+ applyCompanyFilter(query, {
1748
+ isCompanyFeatureEnabled: this.config.isCompanyFeatureEnabled(),
1749
+ entityAlias: this.entityName,
1750
+ }, user);
1751
+ return { query, isRaw: false };
1752
+ }
1753
+
1754
+ // Validate ownership before operations
1755
+ validateCompanyOwnership(entity, user, this.config.isCompanyFeatureEnabled(), 'Product');
1756
+ ```
1757
+
1758
+ ### 7. Error Handling Pattern
1759
+
1760
+ ```typescript
1761
+ import { ErrorHandler } from '@flusys/nestjs-shared';
1762
+
1763
+ // Use ErrorHandler for consistent logging with sensitive data redaction
1764
+ async createUser(dto: CreateUserDto): Promise<User> {
1765
+ try {
1766
+ return await this.repository.save(dto);
1767
+ } catch (error) {
1768
+ ErrorHandler.logAndRethrow(this.logger, error, 'createUser', {
1769
+ entity: 'User',
1770
+ data: dto, // Sensitive fields auto-redacted
1771
+ });
1772
+ }
1773
+ }
1774
+ ```
1775
+
1200
1776
  ---
1201
1777
 
1202
- **Last Updated:** 2026-02-21
1778
+ **Last Updated:** 2026-02-25