@flusys/nestjs-iam 0.1.0-beta.3 → 1.1.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
@@ -2,7 +2,6 @@
2
2
 
3
3
  > **Package:** `@flusys/nestjs-iam`
4
4
  > **Purpose:** Identity and Access Management with RBAC, ABAC, and permission logic
5
- > **Version:** 4.4.0 | **Updated:** 2026-01-24
6
5
 
7
6
  ## Table of Contents
8
7
 
@@ -648,7 +647,8 @@ nestjs-iam/src/
648
647
  ├── interfaces/
649
648
  │ ├── action.interface.ts
650
649
  │ ├── role.interface.ts
651
- └── iam-module-options.interface.ts
650
+ ├── iam-module-options.interface.ts
651
+ │ └── iam-module-async-options.interface.ts
652
652
  ├── enums/
653
653
  │ ├── action-type.enum.ts
654
654
  │ └── permission-type.enum.ts
@@ -658,8 +658,4 @@ nestjs-iam/src/
658
658
 
659
659
  ---
660
660
 
661
- **Last Updated:** 2026-02-09 | **Version:** 4.4.1
662
-
663
- **Recent Changes (2026-02-09):**
664
- - Added explicit `@Inject()` decorators to all services for esbuild bundling compatibility
665
- - All services use REQUEST scope with lazy repository initialization via DataSource Provider pattern
661
+ **Last Updated:** 2026-02-16
@@ -41,7 +41,9 @@ function _ts_param(paramIndex, decorator) {
41
41
  decorator(target, key, paramIndex);
42
42
  };
43
43
  }
44
- let ActionController = class ActionController extends (0, _nestjsshared.createApiController)(_actiondto.CreateActionDto, _actiondto.UpdateActionDto, _actiondto.ActionResponseDto) {
44
+ let ActionController = class ActionController extends (0, _nestjsshared.createApiController)(_actiondto.CreateActionDto, _actiondto.UpdateActionDto, _actiondto.ActionResponseDto, {
45
+ security: 'jwt'
46
+ }) {
45
47
  async getActionsForPermission(user) {
46
48
  const actions = await this.actionService.getActionsForPermission(user);
47
49
  return {
@@ -44,38 +44,15 @@ function _ts_param(paramIndex, decorator) {
44
44
  };
45
45
  }
46
46
  let ActionService = class ActionService extends _classes.RequestScopedApiService {
47
- /**
48
- * Resolve entity class for this service
49
- * @returns Action entity class
50
- */ resolveEntity() {
47
+ resolveEntity() {
51
48
  return _actionentity.Action;
52
49
  }
53
- /**
54
- * Get DataSource provider for this service
55
- * @returns IAMDataSourceProvider instance
56
- */ getDataSourceProvider() {
50
+ getDataSourceProvider() {
57
51
  return this.dataSourceProvider;
58
52
  }
59
- // ==================== Entity Conversion ====================
60
- async convertSingleDtoToEntity(dto, _user) {
61
- if (!('id' in dto) || !dto.id) {
62
- return dto;
63
- }
64
- const existingAction = await this.repository.findOne({
65
- where: {
66
- id: dto.id
67
- }
68
- });
69
- if (!existingAction) {
70
- throw new _common.NotFoundException(`Action with ID ${dto.id} not found`);
71
- }
72
- return {
73
- ...existingAction,
74
- ...dto
75
- };
76
- }
53
+ // Query Customization
77
54
  async getSelectQuery(query, _user, select) {
78
- if (!select || !select.length) {
55
+ if (!select?.length) {
79
56
  select = [
80
57
  'id',
81
58
  'name',
@@ -89,14 +66,13 @@ let ActionService = class ActionService extends _classes.RequestScopedApiService
89
66
  'createdAt'
90
67
  ];
91
68
  }
92
- const selectFields = select.map((field)=>`${this.entityName}.${field}`);
93
- query.select(selectFields);
69
+ query.select(select.map((f)=>`${this.entityName}.${f}`));
94
70
  return {
95
71
  query,
96
72
  isRaw: false
97
73
  };
98
74
  }
99
- async getGlobalSearchQuery(query, search, _user) {
75
+ async getGlobalSearchQuery(query, search) {
100
76
  query.andWhere('(action.name LIKE :search OR action.code LIKE :search OR action.description LIKE :search)', {
101
77
  search: `%${search}%`
102
78
  });
@@ -105,9 +81,8 @@ let ActionService = class ActionService extends _classes.RequestScopedApiService
105
81
  isRaw: false
106
82
  };
107
83
  }
108
- /**
109
- * Override: Convert entity to response DTO
110
- */ convertEntityToResponseDto(entity, _isRaw) {
84
+ // Response Conversion
85
+ convertEntityToResponseDto(entity, _isRaw) {
111
86
  return {
112
87
  id: entity.id,
113
88
  readOnly: entity.readOnly,
@@ -128,12 +103,8 @@ let ActionService = class ActionService extends _classes.RequestScopedApiService
128
103
  deletedById: entity.deletedById
129
104
  };
130
105
  }
131
- /**
132
- * Get actions available for permission assignment (filtered by company whitelist)
133
- *
134
- * @param user - Logged in user info
135
- * @returns Array of actions with id, code, and name fields
136
- */ async getActionsForPermission(user) {
106
+ // Custom Methods
107
+ /** Get actions available for permission assignment (filtered by company whitelist) */ async getActionsForPermission(user) {
137
108
  await this.ensureRepositoryInitialized();
138
109
  if (!user) {
139
110
  throw new Error('User is required for getActionsForPermission');
@@ -227,10 +198,8 @@ let ActionService = class ActionService extends _classes.RequestScopedApiService
227
198
  }
228
199
  return rootNodes;
229
200
  }
230
- // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
231
201
  constructor(cacheManager, utilsService, iamConfigService, dataSourceProvider, permissionService){
232
- // Repository will be set asynchronously by RequestScopedApiService
233
- super('action', null, cacheManager, utilsService, ActionService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "iamConfigService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "permissionService", void 0), _define_property(this, "logger", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.iamConfigService = iamConfigService, this.dataSourceProvider = dataSourceProvider, this.permissionService = permissionService, this.logger = new _common.Logger(ActionService.name);
202
+ super('action', null, cacheManager, utilsService, ActionService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "iamConfigService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "permissionService", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.iamConfigService = iamConfigService, this.dataSourceProvider = dataSourceProvider, this.permissionService = permissionService;
234
203
  }
235
204
  };
236
205
  ActionService = _ts_decorate([
@@ -41,50 +41,33 @@ function _ts_param(paramIndex, decorator) {
41
41
  };
42
42
  }
43
43
  let IAMConfigService = class IAMConfigService {
44
- // ==================== Database Mode ====================
45
- /**
46
- * Get database mode (single or multi-tenant)
47
- */ getDatabaseMode() {
44
+ // Database Mode
45
+ getDatabaseMode() {
48
46
  return this.options.bootstrapAppConfig?.databaseMode ?? 'single';
49
47
  }
50
- /**
51
- * Check if running in multi-tenant mode
52
- */ isMultiTenant() {
48
+ isMultiTenant() {
53
49
  return this.getDatabaseMode() === 'multi-tenant';
54
50
  }
55
- // ==================== Company Feature ====================
56
- /**
57
- * Get enable company feature flag
58
- */ getEnableCompanyFeature() {
51
+ // Company Feature
52
+ getEnableCompanyFeature() {
59
53
  return this.options.bootstrapAppConfig?.enableCompanyFeature ?? false;
60
54
  }
61
- /**
62
- * Check if company feature is enabled (alias for getEnableCompanyFeature)
63
- */ isCompanyFeatureEnabled() {
55
+ isCompanyFeatureEnabled() {
64
56
  return this.getEnableCompanyFeature();
65
57
  }
66
- // ==================== Permission Mode ====================
67
- /**
68
- * Get permission mode from bootstrap config
69
- *
70
- * **Note:** Reads from bootstrapAppConfig (not runtime config)
71
- * because permissionMode affects entity/controller registration.
72
- */ getPermissionMode() {
58
+ // Permission Mode
59
+ getPermissionMode() {
73
60
  return _helpers.PermissionModeHelper.fromString(this.options.bootstrapAppConfig?.permissionMode);
74
61
  }
75
- /**
76
- * Check if RBAC (role-based) permissions are enabled
77
- */ isRbacEnabled() {
62
+ isRbacEnabled() {
78
63
  const mode = this.getPermissionMode();
79
64
  return mode === _permissiontypeenum.IAMPermissionMode.RBAC || mode === _permissiontypeenum.IAMPermissionMode.FULL;
80
65
  }
81
- /**
82
- * Check if direct (user-level) permissions are enabled
83
- */ isDirectPermissionEnabled() {
66
+ isDirectPermissionEnabled() {
84
67
  const mode = this.getPermissionMode();
85
68
  return mode === _permissiontypeenum.IAMPermissionMode.DIRECT || mode === _permissiontypeenum.IAMPermissionMode.FULL;
86
69
  }
87
- // ==================== Options ====================
70
+ // Options
88
71
  getOptions() {
89
72
  return this.options;
90
73
  }
@@ -83,10 +83,8 @@ function _ts_param(paramIndex, decorator) {
83
83
  };
84
84
  }
85
85
  let IAMDataSourceProvider = class IAMDataSourceProvider extends _modules.MultiTenantDataSourceService {
86
- // ==================== Factory Methods ====================
87
- /**
88
- * Build parent options from IAMModuleOptions
89
- */ static buildParentOptions(options) {
86
+ // Factory Methods
87
+ static buildParentOptions(options) {
90
88
  return {
91
89
  bootstrapAppConfig: options.bootstrapAppConfig,
92
90
  defaultDatabaseConfig: options.config?.defaultDatabaseConfig,
@@ -94,43 +92,29 @@ let IAMDataSourceProvider = class IAMDataSourceProvider extends _modules.MultiTe
94
92
  tenants: options.config?.tenants
95
93
  };
96
94
  }
97
- // ==================== Feature Flags ====================
98
- /**
99
- * Get global enable company feature flag
100
- */ getEnableCompanyFeature() {
95
+ // Feature Flags
96
+ getEnableCompanyFeature() {
101
97
  return this.iamOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
102
98
  }
103
- /**
104
- * Get enable company feature for specific tenant
105
- * Falls back to global setting if not specified per-tenant
106
- */ getEnableCompanyFeatureForTenant(tenant) {
99
+ getEnableCompanyFeatureForTenant(tenant) {
107
100
  return tenant?.enableCompanyFeature ?? this.getEnableCompanyFeature();
108
101
  }
109
- /**
110
- * Get enable company feature for current request context
111
- */ getEnableCompanyFeatureForCurrentTenant() {
102
+ getEnableCompanyFeatureForCurrentTenant() {
112
103
  return this.getEnableCompanyFeatureForTenant(this.getCurrentTenant() ?? undefined);
113
104
  }
114
- // ==================== Entity Management ====================
115
- /**
116
- * Get IAM entities dynamically based on configuration
117
- * Returns appropriate entities based on company feature and permission mode
118
- */ async getIAMEntities() {
105
+ // Entity Management
106
+ async getIAMEntities() {
119
107
  const { Action, Role, RoleWithCompany, UserIamPermission, UserIamPermissionWithCompany, getIAMEntitiesByConfig } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
120
108
  const enableCompanyFeature = this.getEnableCompanyFeatureForCurrentTenant();
121
109
  const permissionMode = this.iamOptions.bootstrapAppConfig?.permissionMode || 'FULL';
122
110
  return getIAMEntitiesByConfig(enableCompanyFeature, permissionMode);
123
111
  }
124
- // ==================== Overrides ====================
125
- /**
126
- * Override to dynamically set entities based on tenant config
127
- */ async createDataSourceFromConfig(config) {
112
+ // Overrides
113
+ async createDataSourceFromConfig(config) {
128
114
  const entities = await this.getIAMEntities();
129
115
  return super.createDataSourceFromConfig(config, entities);
130
116
  }
131
- /**
132
- * Override to use IAM-specific static cache
133
- */ async getSingleDataSource() {
117
+ async getSingleDataSource() {
134
118
  if (!IAMDataSourceProvider.singleDataSource) {
135
119
  if (IAMDataSourceProvider.singleConnectionLock) {
136
120
  return IAMDataSourceProvider.singleConnectionLock;
@@ -154,9 +138,7 @@ let IAMDataSourceProvider = class IAMDataSourceProvider extends _modules.MultiTe
154
138
  }
155
139
  return IAMDataSourceProvider.singleDataSource;
156
140
  }
157
- /**
158
- * Override to use IAM-specific static cache for tenant connections
159
- */ async getOrCreateTenantConnection(tenant) {
141
+ async getOrCreateTenantConnection(tenant) {
160
142
  // Return existing initialized connection from IAM-specific cache
161
143
  const existing = IAMDataSourceProvider.tenantConnections.get(tenant.id);
162
144
  if (existing?.isInitialized) {
@@ -38,33 +38,23 @@ function _ts_param(paramIndex, decorator) {
38
38
  };
39
39
  }
40
40
  let PermissionCacheService = class PermissionCacheService {
41
- // ==================== Cache Key Generation ====================
42
- /**
43
- * Generate cache key for user permissions (backend codes for PermissionGuard)
44
- * Format matches PermissionGuard in nestjs-shared
45
- */ generateCacheKey(options) {
41
+ // Cache Key Generation
42
+ generateCacheKey(options) {
46
43
  const { userId, companyId, branchId, enableCompanyFeature } = options;
47
44
  if (enableCompanyFeature && companyId) {
48
45
  return `${this.CACHE_PREFIX}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`;
49
46
  }
50
47
  return `${this.CACHE_PREFIX}:user:${userId}`;
51
48
  }
52
- /**
53
- * Generate cache key for full my-permissions response
54
- */ generateMyPermissionsCacheKey(options) {
49
+ generateMyPermissionsCacheKey(options) {
55
50
  const { userId, companyId, branchId, enableCompanyFeature } = options;
56
51
  if (enableCompanyFeature && companyId) {
57
52
  return `${this.MY_PERMISSIONS_PREFIX}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`;
58
53
  }
59
54
  return `${this.MY_PERMISSIONS_PREFIX}:user:${userId}`;
60
55
  }
61
- // ==================== Cache Operations ====================
62
- /**
63
- * Set user permissions in cache
64
- *
65
- * @param options - Cache key options
66
- * @param permissions - Array of permission codes (action codes)
67
- */ async setPermissions(options, permissions) {
56
+ // Cache Operations
57
+ async setPermissions(options, permissions) {
68
58
  try {
69
59
  const key = this.generateCacheKey(options);
70
60
  await this.cacheManager.set(key, permissions, this.TTL);
@@ -74,12 +64,7 @@ let PermissionCacheService = class PermissionCacheService {
74
64
  // Don't throw - cache failure shouldn't break the operation
75
65
  }
76
66
  }
77
- /**
78
- * Get user permissions from cache
79
- *
80
- * @param options - Cache key options
81
- * @returns Array of permission codes or null if not found
82
- */ async getPermissions(options) {
67
+ async getPermissions(options) {
83
68
  try {
84
69
  const key = this.generateCacheKey(options);
85
70
  const result = await this.cacheManager.get(key);
@@ -89,14 +74,8 @@ let PermissionCacheService = class PermissionCacheService {
89
74
  return null;
90
75
  }
91
76
  }
92
- // ==================== My-Permissions Cache Operations ====================
93
- /**
94
- * Set full my-permissions response in cache
95
- * Caches frontend actions and backend codes for quick retrieval
96
- *
97
- * @param options - Cache key options
98
- * @param data - Full permissions data to cache
99
- */ async setMyPermissions(options, data) {
77
+ // My-Permissions Cache Operations
78
+ async setMyPermissions(options, data) {
100
79
  try {
101
80
  const key = this.generateMyPermissionsCacheKey(options);
102
81
  await this.cacheManager.set(key, data, this.TTL);
@@ -105,12 +84,7 @@ let PermissionCacheService = class PermissionCacheService {
105
84
  this.logger.error(`Failed to cache my-permissions: ${error}`);
106
85
  }
107
86
  }
108
- /**
109
- * Get full my-permissions response from cache
110
- *
111
- * @param options - Cache key options
112
- * @returns Cached permissions data or null if not found
113
- */ async getMyPermissions(options) {
87
+ async getMyPermissions(options) {
114
88
  try {
115
89
  const key = this.generateMyPermissionsCacheKey(options);
116
90
  const result = await this.cacheManager.get(key);
@@ -123,13 +97,8 @@ let PermissionCacheService = class PermissionCacheService {
123
97
  return null;
124
98
  }
125
99
  }
126
- // ==================== Action Code Cache Operations ====================
127
- /**
128
- * Cache action codes to IDs mapping
129
- * Used for parentCodes filter to avoid DB query on cache hit
130
- *
131
- * @param codeToIdMap - Map of action code to action ID
132
- */ async setActionCodeMap(codeToIdMap) {
100
+ // Action Code Cache Operations
101
+ async setActionCodeMap(codeToIdMap) {
133
102
  try {
134
103
  const key = `${this.ACTION_CODE_PREFIX}:map`;
135
104
  await this.cacheManager.set(key, codeToIdMap, this.ACTION_CODE_TTL);
@@ -138,12 +107,7 @@ let PermissionCacheService = class PermissionCacheService {
138
107
  this.logger.error(`Failed to cache action code map: ${error}`);
139
108
  }
140
109
  }
141
- /**
142
- * Get action IDs for given codes from cache
143
- *
144
- * @param codes - Array of action codes
145
- * @returns Map of code to ID, or null if not cached
146
- */ async getActionIdsByCodes(codes) {
110
+ async getActionIdsByCodes(codes) {
147
111
  try {
148
112
  const key = `${this.ACTION_CODE_PREFIX}:map`;
149
113
  const fullMap = await this.cacheManager.get(key);
@@ -163,10 +127,7 @@ let PermissionCacheService = class PermissionCacheService {
163
127
  return null;
164
128
  }
165
129
  }
166
- /**
167
- * Invalidate action code cache
168
- * Call when actions are created, updated, or deleted
169
- */ async invalidateActionCodeCache() {
130
+ async invalidateActionCodeCache() {
170
131
  try {
171
132
  const key = `${this.ACTION_CODE_PREFIX}:map`;
172
133
  await this.cacheManager.del(key);
@@ -175,15 +136,8 @@ let PermissionCacheService = class PermissionCacheService {
175
136
  this.logger.warn(`Failed to invalidate action code cache: ${error}`);
176
137
  }
177
138
  }
178
- // ==================== Cache Invalidation ====================
179
- /**
180
- * Invalidate cache for specific user
181
- * Clears both permission codes and my-permissions cache for ALL branches
182
- *
183
- * @param userId - User ID
184
- * @param companyId - Optional company ID
185
- * @param branchIds - Optional array of branch IDs to invalidate (if not provided, only null branch is cleared)
186
- */ async invalidateUser(userId, companyId, branchIds) {
139
+ // Cache Invalidation
140
+ async invalidateUser(userId, companyId, branchIds) {
187
141
  try {
188
142
  const keysToDelete = [
189
143
  // Permission codes cache (for PermissionGuard) - user-based key
@@ -209,15 +163,7 @@ let PermissionCacheService = class PermissionCacheService {
209
163
  this.logger.warn(`Failed to invalidate user cache for ${userId}: ${error}`);
210
164
  }
211
165
  }
212
- /**
213
- * Invalidate cache for multiple users
214
- * Useful when role or company permissions change
215
- *
216
- * @param userIds - Array of user IDs
217
- * @param companyId - Optional company ID
218
- * @param branchIds - Optional array of branch IDs to invalidate for each user
219
- * @returns Number of users whose cache was invalidated
220
- */ async invalidateUsers(userIds, companyId, branchIds) {
166
+ async invalidateUsers(userIds, companyId, branchIds) {
221
167
  if (userIds.length === 0) {
222
168
  return 0;
223
169
  }
@@ -233,30 +179,14 @@ let PermissionCacheService = class PermissionCacheService {
233
179
  }
234
180
  return successCount;
235
181
  }
236
- /**
237
- * Invalidate cache for all users in company
238
- * WARNING: Without pattern matching support in HybridCache, this method
239
- * invalidates individual user caches passed in the userIds array.
240
- * For true company-wide invalidation, consider upgrading HybridCache with keys() support.
241
- *
242
- * @param companyId - Company ID
243
- * @returns Number of cache entries deleted (always 0 without keys() support)
244
- */ async invalidateCompany(companyId) {
182
+ /** Invalidate cache for all users in company (requires userIds via invalidateUsers) */ async invalidateCompany(companyId) {
245
183
  // Note: HybridCache doesn't support pattern matching (keys() method)
246
184
  // Company-wide invalidation requires passing individual user IDs
247
185
  // This is a placeholder that logs a warning
248
186
  this.logger.warn(`invalidateCompany called for ${companyId}, but pattern matching is not supported. ` + `Use invalidateUsers() with specific user IDs instead.`);
249
187
  return 0;
250
188
  }
251
- /**
252
- * Invalidate cache for all users with specific role
253
- *
254
- * @param roleId - Role ID (for logging)
255
- * @param userIds - Array of user IDs who have this role
256
- * @param companyId - Optional company ID
257
- * @param branchIds - Optional array of branch IDs to invalidate
258
- * @returns Number of users whose cache was invalidated
259
- */ async invalidateRole(roleId, userIds, companyId, branchIds) {
189
+ async invalidateRole(roleId, userIds, companyId, branchIds) {
260
190
  if (userIds.length === 0) {
261
191
  this.logger.debug(`No users found for role ${roleId}`);
262
192
  return 0;
@@ -267,12 +197,8 @@ let PermissionCacheService = class PermissionCacheService {
267
197
  }
268
198
  return count;
269
199
  }
270
- // ==================== Administrative Operations ====================
271
- /**
272
- * Clear all permission caches
273
- * Uses HybridCache reset methods (memory and redis)
274
- * WARNING: Use with caution - this affects all caches, not just permissions
275
- */ async clearAll() {
200
+ // Administrative Operations
201
+ /** Clear all permission caches (memory and redis) */ async clearAll() {
276
202
  try {
277
203
  await this.cacheManager.reset(); // Clear memory cache
278
204
  await this.cacheManager.resetL2(); // Clear redis cache