@flusys/nestjs-shared 0.1.0-beta.2 → 1.0.0-beta

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
@@ -32,8 +32,8 @@ This comprehensive guide covers the shared package - the shared NestJS infrastru
32
32
  - **Generic CRUD** - Standardized API controller and service patterns
33
33
  - **Permission System** - Role and permission-based access control
34
34
  - **Caching** - In-memory + Redis hybrid caching
35
- - **Request Correlation** - AsyncLocalStorage-based request tracking (NEW)
36
- - **Middleware** - Logging, correlation, and performance monitoring (NEW)
35
+ - **Request Correlation** - AsyncLocalStorage-based request tracking
36
+ - **Middleware** - Logging, correlation, and performance monitoring
37
37
  - **Interceptors** - Response metadata, idempotency, auto field setting
38
38
  - **Multi-Tenancy** - Dynamic database connection management
39
39
  - **Error Handling** - Centralized error handling utilities
@@ -462,23 +462,52 @@ export class ReportsController {
462
462
  }
463
463
  ```
464
464
 
465
- ### @RequireAllPermissions
465
+ ### @RequirePermissionCondition
466
466
 
467
- Require all listed permissions:
467
+ Build complex permission conditions with nested AND/OR logic:
468
468
 
469
469
  ```typescript
470
- import { RequireAllPermissions } from '@flusys/nestjs-shared/decorators';
470
+ import { RequirePermissionCondition } from '@flusys/nestjs-shared/decorators';
471
471
 
472
472
  @Controller('sensitive')
473
473
  export class SensitiveController {
474
- @RequireAllPermissions('admin.access', 'security.clearance')
475
- @Get()
476
- getSensitiveData() {
477
- // Requires BOTH permissions
478
- }
474
+ // Simple: User needs 'admin' OR 'manager'
475
+ @RequirePermissionCondition({
476
+ operator: 'or',
477
+ permissions: ['admin', 'manager'],
478
+ })
479
+ @Get('simple')
480
+ getSimpleData() {}
481
+
482
+ // Complex: User needs 'users.read' AND ('admin' OR 'manager')
483
+ @RequirePermissionCondition({
484
+ operator: 'and',
485
+ permissions: ['users.read'],
486
+ children: [
487
+ { operator: 'or', permissions: ['admin', 'manager'] }
488
+ ]
489
+ })
490
+ @Get('complex')
491
+ getComplexData() {}
492
+
493
+ // Very complex: (A AND B) OR (C AND D)
494
+ @RequirePermissionCondition({
495
+ operator: 'or',
496
+ children: [
497
+ { operator: 'and', permissions: ['finance.read', 'finance.write'] },
498
+ { operator: 'and', permissions: ['admin.full', 'reports.access'] }
499
+ ]
500
+ })
501
+ @Get('very-complex')
502
+ getVeryComplexData() {}
479
503
  }
480
504
  ```
481
505
 
506
+ **Note:** For simple "require ALL permissions" use case, use `@RequirePermission` which defaults to AND logic:
507
+ ```typescript
508
+ @RequirePermission('admin.access', 'security.clearance') // User needs BOTH
509
+ ```
510
+
482
511
  ---
483
512
 
484
513
  ## Guards
@@ -586,7 +615,7 @@ PermissionModule.forRoot({
586
615
 
587
616
  ## Middleware
588
617
 
589
- ### LoggerMiddleware (NEW - 2026-01-12, Enhanced - 2026-01-14)
618
+ ### LoggerMiddleware
590
619
 
591
620
  **Location:** `@flusys/nestjs-shared/middlewares/logger.middleware.ts`
592
621
 
@@ -1235,11 +1264,12 @@ import {
1235
1264
  Public,
1236
1265
  RequirePermission,
1237
1266
  RequireAnyPermission,
1238
- RequireAllPermissions,
1267
+ RequirePermissionCondition,
1239
1268
  } from '@flusys/nestjs-shared/decorators';
1240
1269
 
1241
1270
  // Guards
1242
1271
  import {
1272
+ JwtAuthGuard,
1243
1273
  PermissionGuard,
1244
1274
  } from '@flusys/nestjs-shared/guards';
1245
1275
 
@@ -1375,25 +1405,17 @@ async getPermissions(userId: string) {
1375
1405
 
1376
1406
  The `@flusys/nestjs-shared` package provides:
1377
1407
 
1378
- **Generic CRUD** - Consistent API patterns
1379
- **Permission System** - Flexible access control
1380
- **Caching** - High-performance data access
1381
- **Multi-Tenancy** - Database isolation
1382
- **Request Correlation** - AsyncLocalStorage-based tracking (NEW)
1383
- **Middleware** - Logging and performance monitoring (NEW)
1384
- **Interceptors** - Request/response processing
1385
- **Error Handling** - Centralized error management
1408
+ - **Generic CRUD** - Consistent API patterns
1409
+ - **Permission System** - Flexible access control
1410
+ - **Caching** - High-performance data access
1411
+ - **Multi-Tenancy** - Database isolation
1412
+ - **Request Correlation** - AsyncLocalStorage-based tracking
1413
+ - **Middleware** - Logging and performance monitoring
1414
+ - **Interceptors** - Request/response processing
1415
+ - **Error Handling** - Centralized error management
1386
1416
 
1387
1417
  This is the shared infrastructure layer used by all other Flusys packages.
1388
1418
 
1389
1419
  ---
1390
1420
 
1391
- **Last Updated:** 2026-02-07
1392
- **Recent Improvements:**
1393
- - 2026-02-07: Documentation cleanup - removed outdated JWT configuration section, updated package architecture to match actual structure
1394
- - 2026-01-14: Enhanced `LoggerMiddleware` with complete request/response details (URL, path, query, headers, user context)
1395
- - 2026-01-14: Added multiple response hooks (send, json, end) for reliable response capture
1396
- - 2026-01-14: Improved error logging with stack traces
1397
- - 2026-01-12: Added `LoggerMiddleware` for request correlation and structured logging
1398
- - 2026-01-12: Added AsyncLocalStorage-based request context (requestId, tenantId, userId, companyId)
1399
- - 2026-01-13: Removed JWT config/constants (moved to auth-specific packages)
1421
+ **Last Updated:** 2026-02-16
@@ -117,9 +117,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
117
117
  delete: isGlobalSecurity ? defaultSecurity : normalizeSecurity(securityConfig?.delete)
118
118
  };
119
119
  let ApiController = class ApiController {
120
- // =========================================================================
121
- // INSERT (Single Item) - With Idempotency Support
122
- // =========================================================================
123
120
  async insert(addDto, user) {
124
121
  const entity = await this.service.insert(addDto, user);
125
122
  const data = (0, _classtransformer.plainToInstance)(responseDtoClass, entity);
@@ -129,9 +126,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
129
126
  data
130
127
  };
131
128
  }
132
- // =========================================================================
133
- // INSERT MANY (Bulk) - With Idempotency Support
134
- // =========================================================================
135
129
  async insertMany(addDto, user) {
136
130
  const entities = await this.service.insertMany(addDto, user);
137
131
  const data = entities.map((item)=>(0, _classtransformer.plainToInstance)(responseDtoClass, item));
@@ -146,9 +140,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
146
140
  }
147
141
  };
148
142
  }
149
- // =========================================================================
150
- // GET BY ID (Single Item)
151
- // =========================================================================
152
143
  async getById(id, body, user) {
153
144
  const entity = await this.service.findById(id, user, body?.select);
154
145
  const data = (0, _classtransformer.plainToInstance)(responseDtoClass, entity);
@@ -158,9 +149,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
158
149
  data
159
150
  };
160
151
  }
161
- // =========================================================================
162
- // UPDATE (Single Item)
163
- // =========================================================================
164
152
  async update(updateDto, user) {
165
153
  const entity = await this.service.update(updateDto, user);
166
154
  const data = (0, _classtransformer.plainToInstance)(responseDtoClass, entity);
@@ -170,9 +158,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
170
158
  data
171
159
  };
172
160
  }
173
- // =========================================================================
174
- // UPDATE MANY (Bulk)
175
- // =========================================================================
176
161
  async updateMany(updateDtos, user) {
177
162
  const entities = await this.service.updateMany(updateDtos, user);
178
163
  const data = (0, _classtransformer.plainToInstance)(responseDtoClass, entities);
@@ -187,9 +172,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
187
172
  }
188
173
  };
189
174
  }
190
- // =========================================================================
191
- // GET ALL (Paginated List)
192
- // =========================================================================
193
175
  async getAll(filterAndPaginationDto, user, search) {
194
176
  const result = await this.service.getAll(search ?? '', filterAndPaginationDto, user);
195
177
  const data = (0, _classtransformer.plainToInstance)(responseDtoClass, result.data);
@@ -210,9 +192,6 @@ function createApiController(createDtoClass, updateDtoClass, responseDtoClass, o
210
192
  }
211
193
  };
212
194
  }
213
- // =========================================================================
214
- // DELETE (Soft/Restore/Permanent)
215
- // =========================================================================
216
195
  async delete(deleteDto, user) {
217
196
  await this.service.delete(deleteDto, user);
218
197
  const count = Array.isArray(deleteDto.id) ? deleteDto.id.length : 1;
@@ -25,9 +25,6 @@ function _define_property(obj, key, value) {
25
25
  return obj;
26
26
  }
27
27
  let ApiService = class ApiService {
28
- // ---------------------------------------------------------------------
29
- // INSERT SINGLE ENTITY
30
- // ---------------------------------------------------------------------
31
28
  async insert(dto, user) {
32
29
  await this.ensureRepositoryInitialized();
33
30
  const qr = this.repository.manager.connection.createQueryRunner();
@@ -54,9 +51,6 @@ let ApiService = class ApiService {
54
51
  await qr.release();
55
52
  }
56
53
  }
57
- // ---------------------------------------------------------------------
58
- // INSERT MULTIPLE ENTITIES
59
- // ---------------------------------------------------------------------
60
54
  async insertMany(dtos, user) {
61
55
  await this.ensureRepositoryInitialized();
62
56
  const qr = this.repository.manager.connection.createQueryRunner();
@@ -87,9 +81,6 @@ let ApiService = class ApiService {
87
81
  await qr.release();
88
82
  }
89
83
  }
90
- // ---------------------------------------------------------------------
91
- // UPDATE SINGLE ENTITY
92
- // ---------------------------------------------------------------------
93
84
  async update(dto, user) {
94
85
  await this.ensureRepositoryInitialized();
95
86
  const qr = this.repository.manager.connection.createQueryRunner();
@@ -116,9 +107,6 @@ let ApiService = class ApiService {
116
107
  await qr.release();
117
108
  }
118
109
  }
119
- // ---------------------------------------------------------------------
120
- // UPDATE MULTIPLE ENTITIES
121
- // ---------------------------------------------------------------------
122
110
  async updateMany(dtos, user) {
123
111
  await this.ensureRepositoryInitialized();
124
112
  const qr = this.repository.manager.connection.createQueryRunner();
@@ -149,9 +137,6 @@ let ApiService = class ApiService {
149
137
  await qr.release();
150
138
  }
151
139
  }
152
- // ---------------------------------------------------------------------
153
- // FIND BY IDS
154
- // ---------------------------------------------------------------------
155
140
  async findByIds(ids, user) {
156
141
  await this.ensureRepositoryInitialized();
157
142
  try {
@@ -176,9 +161,6 @@ let ApiService = class ApiService {
176
161
  this.handleError(error, 'findByIds');
177
162
  }
178
163
  }
179
- // ---------------------------------------------------------------------
180
- // FIND BY ID
181
- // ---------------------------------------------------------------------
182
164
  async findById(id, user, select) {
183
165
  await this.ensureRepositoryInitialized();
184
166
  try {
@@ -217,9 +199,6 @@ let ApiService = class ApiService {
217
199
  this.handleError(error, 'findById');
218
200
  }
219
201
  }
220
- // ---------------------------------------------------------------------
221
- // GET ALL (WITH FILTER, SORT, SEARCH, PAGINATION & CACHING)
222
- // ---------------------------------------------------------------------
223
202
  async getAll(search, filterAndPaginationDto, user) {
224
203
  await this.ensureRepositoryInitialized();
225
204
  try {
@@ -318,9 +297,6 @@ let ApiService = class ApiService {
318
297
  this.handleError(error, 'getAll');
319
298
  }
320
299
  }
321
- // ---------------------------------------------------------------------
322
- // DELETE / RESTORE / HARD DELETE
323
- // ---------------------------------------------------------------------
324
300
  async delete(option, user) {
325
301
  await this.ensureRepositoryInitialized();
326
302
  const queryRunner = this.repository.manager.connection.createQueryRunner();
@@ -363,9 +339,7 @@ let ApiService = class ApiService {
363
339
  await queryRunner.release();
364
340
  }
365
341
  }
366
- // ---------------------------------------------------------------------
367
- // CACHING HELPERS
368
- // ---------------------------------------------------------------------
342
+ // Caching
369
343
  async clearCacheForAll() {
370
344
  await this.utilsService.clearCache(this.entityName, this.cacheManager);
371
345
  }
@@ -380,17 +354,8 @@ let ApiService = class ApiService {
380
354
  });
381
355
  _errorhandlerutil.ErrorHandler.rethrowError(error);
382
356
  }
383
- // ---------------------------------------------------------------------
384
- // HOOKS / HELPERS (OVERRIDE IN CHILD CLASSES)
385
- // ---------------------------------------------------------------------
386
- /**
387
- * Hook called before ANY repository access
388
- * CRITICAL: Override this in REQUEST-scoped services that use lazy repository initialization
389
- * Example use case: DataSource Provider pattern where repository is set dynamically
390
- */ async ensureRepositoryInitialized() {
391
- // Default: no-op - repository is already initialized in constructor
392
- // Override in child classes that need lazy initialization
393
- }
357
+ // Hooks (override in child classes)
358
+ async ensureRepositoryInitialized() {}
394
359
  async beforeInsertOperation(_dto, _user, _queryRunner) {}
395
360
  async afterInsertOperation(_entity, _user, _queryRunner) {}
396
361
  async beforeUpdateOperation(_dto, _user, _queryRunner) {}
@@ -439,9 +404,7 @@ let ApiService = class ApiService {
439
404
  isRaw: false
440
405
  };
441
406
  }
442
- // ---------------------------------------------------------------------
443
- // DTO <-> ENTITY CONVERSION
444
- // ---------------------------------------------------------------------
407
+ // DTO conversion
445
408
  async convertRequestDtoToEntity(dto, user) {
446
409
  return Array.isArray(dto) ? await this.convertArrayDtoToEntities(dto, user) : [
447
410
  await this.convertSingleDtoToEntity(dto, user)
@@ -30,18 +30,7 @@ function _ts_decorate(decorators, target, key, desc) {
30
30
  return c > 3 && r && Object.defineProperty(target, key, r), r;
31
31
  }
32
32
  let RequestScopedApiService = class RequestScopedApiService extends _apiserviceclass.ApiService {
33
- /**
34
- * Ensures repository is initialized before any database access
35
- * CRITICAL: Automatically called by ApiService before all CRUD operations
36
- *
37
- * This method:
38
- * 1. Calls resolveEntity() to get the entity class
39
- * 2. Calls getDataSourceProvider() to get the provider
40
- * 3. Loads the repository from the provider
41
- * 4. Marks initialization as complete
42
- *
43
- * @internal - Called automatically, do not call manually
44
- */ async ensureRepositoryInitialized() {
33
+ async ensureRepositoryInitialized() {
45
34
  if (!this.repositoryInitialized) {
46
35
  const entity = this.resolveEntity();
47
36
  const provider = this.getDataSourceProvider();
@@ -49,26 +38,7 @@ let RequestScopedApiService = class RequestScopedApiService extends _apiservicec
49
38
  this.repositoryInitialized = true;
50
39
  }
51
40
  }
52
- /**
53
- * Helper method to initialize additional repositories
54
- * Useful when service needs multiple repositories beyond the primary entity repository
55
- *
56
- * @param entities - Array of entity classes to load repositories for
57
- * @returns Array of initialized repositories in the same order as entities
58
- *
59
- * @example
60
- * ```typescript
61
- * protected override async ensureRepositoryInitialized(): Promise<void> {
62
- * await super.ensureRepositoryInitialized();
63
- *
64
- * if (this.configService.isCompanyFeatureEnabled() && !this.permissionRepository) {
65
- * [this.permissionRepository] = await this.initializeAdditionalRepositories([
66
- * UserCompanyPermission
67
- * ]);
68
- * }
69
- * }
70
- * ```
71
- */ async initializeAdditionalRepositories(entities) {
41
+ /** Initialize additional repositories beyond the primary entity */ async initializeAdditionalRepositories(entities) {
72
42
  const provider = this.getDataSourceProvider();
73
43
  const repositories = [];
74
44
  for (const entity of entities){
@@ -76,31 +46,12 @@ let RequestScopedApiService = class RequestScopedApiService extends _apiservicec
76
46
  }
77
47
  return repositories;
78
48
  }
79
- /**
80
- * Helper method to get the DataSource
81
- * Useful when service needs direct DataSource access for transactions or custom queries
82
- *
83
- * @returns DataSource instance
84
- *
85
- * @example
86
- * ```typescript
87
- * protected override async ensureRepositoryInitialized(): Promise<void> {
88
- * await super.ensureRepositoryInitialized();
89
- *
90
- * if (!this.dataSource) {
91
- * this.dataSource = await this.getDataSourceForService();
92
- * }
93
- * }
94
- * ```
95
- */ async getDataSourceForService() {
49
+ /** Get DataSource for direct access (transactions, custom queries) */ async getDataSourceForService() {
96
50
  const provider = this.getDataSourceProvider();
97
51
  return await provider.getDataSource();
98
52
  }
99
53
  constructor(...args){
100
- super(...args), /**
101
- * Tracks whether repository has been initialized
102
- * Automatically managed by ensureRepositoryInitialized()
103
- */ _define_property(this, "repositoryInitialized", false);
54
+ super(...args), _define_property(this, "repositoryInitialized", false);
104
55
  }
105
56
  };
106
57
  RequestScopedApiService = _ts_decorate([
@@ -70,9 +70,7 @@ function _interop_require_wildcard(obj, nodeInterop) {
70
70
  }
71
71
  const Transport = _winstontransport.default || _winstontransport;
72
72
  const DailyRotateFile = _winstondailyrotatefile.default || _winstondailyrotatefile;
73
- // =============================================================================
74
- // CONFIGURATION (from envConfig)
75
- // =============================================================================
73
+ // Configuration
76
74
  const logConfig = _config.envConfig.getLogConfig();
77
75
  const LOG_DIR = logConfig.dir;
78
76
  const LOG_LEVEL = logConfig.level;
@@ -85,9 +83,7 @@ if (!(0, _fs.existsSync)(LOG_DIR)) {
85
83
  recursive: true
86
84
  });
87
85
  }
88
- // =============================================================================
89
- // CUSTOM FORMATS
90
- // =============================================================================
86
+ // Custom Formats
91
87
  /**
92
88
  * Custom format for structured logging
93
89
  * Includes: timestamp, level, context, requestId, userId, message, metadata
@@ -117,9 +113,7 @@ if (!(0, _fs.existsSync)(LOG_DIR)) {
117
113
  const stackTrace = stack ? `\n${stack}` : '';
118
114
  return `${timestamp} [${level.toUpperCase().padEnd(7)}] [${ctx}]${endpoint}${status}${time}${reqId}${user} ${message}${stackTrace}`;
119
115
  });
120
- // =============================================================================
121
- // TRANSPORTS
122
- // =============================================================================
116
+ // Transports
123
117
  /**
124
118
  * Daily rotating file transport for all logs
125
119
  */ const combinedRotateTransport = new DailyRotateFile({
@@ -140,9 +134,7 @@ if (!(0, _fs.existsSync)(LOG_DIR)) {
140
134
  maxFiles: LOG_MAX_FILES,
141
135
  level: 'error'
142
136
  });
143
- // =============================================================================
144
- // TENANT-AWARE TRANSPORT
145
- // =============================================================================
137
+ // Tenant-Aware Transport
146
138
  /**
147
139
  * Cache for tenant-specific transports
148
140
  * Avoids creating new transport instances for each log entry
@@ -208,9 +200,7 @@ if (!(0, _fs.existsSync)(LOG_DIR)) {
208
200
  stack: true
209
201
  }), devConsoleFormat)
210
202
  });
211
- // =============================================================================
212
- // LOGGER INSTANCES
213
- // =============================================================================
203
+ // Logger Instances
214
204
  /**
215
205
  * Development logger configuration
216
206
  * - Console output with colors
@@ -1,14 +1,5 @@
1
- /**
2
- * Constants for the common module
3
- *
4
- * This file centralizes all constants used across the module:
5
- * - Metadata keys for decorators
6
- * - Injection tokens for DI
7
- * - Header names
8
- */ // =============================================================================
9
- // METADATA KEYS (used with @SetMetadata decorator)
10
- // =============================================================================
11
- /** Metadata key for public routes (skip authentication) */ "use strict";
1
+ // Metadata keys
2
+ "use strict";
12
3
  Object.defineProperty(exports, "__esModule", {
13
4
  value: true
14
5
  });