@flusys/nestjs-shared 1.1.0-beta → 1.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.md +146 -1330
  2. package/cjs/classes/api-controller.class.js +325 -49
  3. package/cjs/classes/api-service.class.js +271 -149
  4. package/cjs/classes/handle-custome-db.js +37 -0
  5. package/cjs/classes/index.js +2 -2
  6. package/cjs/classes/winston.logger.class.js +103 -70
  7. package/cjs/constants/index.js +15 -4
  8. package/cjs/constants/message-keys.js +68 -0
  9. package/cjs/constants/permissions.js +287 -0
  10. package/cjs/decorators/api-response.decorator.js +2 -3
  11. package/cjs/decorators/index.js +1 -0
  12. package/cjs/decorators/log-action.decorator.js +149 -0
  13. package/cjs/decorators/require-permission.decorator.js +5 -5
  14. package/cjs/dtos/delete.dto.js +10 -0
  15. package/cjs/dtos/filter-and-pagination.dto.js +24 -34
  16. package/cjs/dtos/get-by-ids.dto.js +65 -0
  17. package/cjs/dtos/identity-response.dto.js +4 -8
  18. package/cjs/dtos/index.js +1 -0
  19. package/cjs/dtos/pagination.dto.js +4 -8
  20. package/cjs/dtos/response-payload.dto.js +68 -112
  21. package/cjs/entities/identity.js +12 -6
  22. package/cjs/entities/index.js +1 -0
  23. package/cjs/entities/raw-type.js +4 -0
  24. package/cjs/entities/user-root.js +15 -15
  25. package/cjs/enums/index.js +20 -0
  26. package/cjs/enums/notification-type.enum.js +17 -0
  27. package/cjs/enums/participant-status.enum.js +17 -0
  28. package/cjs/enums/recurrence-type.enum.js +18 -0
  29. package/cjs/exceptions/base-app.exception.js +151 -0
  30. package/cjs/exceptions/index.js +1 -0
  31. package/cjs/exceptions/permission.exception.js +13 -9
  32. package/cjs/filters/global-exception.filter.js +279 -0
  33. package/cjs/filters/index.js +18 -0
  34. package/cjs/guards/jwt-auth.guard.js +4 -1
  35. package/cjs/guards/permission.guard.js +88 -148
  36. package/cjs/index.js +2 -0
  37. package/cjs/interceptors/idempotency.interceptor.js +1 -1
  38. package/cjs/interceptors/index.js +1 -4
  39. package/cjs/interceptors/set-user-field-on-body.interceptor.js +60 -0
  40. package/cjs/interceptors/slug.interceptor.js +30 -9
  41. package/cjs/interfaces/datasource.interface.js +4 -0
  42. package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
  43. package/cjs/interfaces/index.js +4 -2
  44. package/cjs/interfaces/module-config.interface.js +4 -0
  45. package/cjs/interfaces/notification-adapter.interface.js +11 -0
  46. package/cjs/interfaces/permission.interface.js +1 -1
  47. package/cjs/middlewares/logger.middleware.js +133 -115
  48. package/cjs/modules/cache/cache.module.js +3 -3
  49. package/cjs/modules/datasource/index.js +0 -1
  50. package/cjs/modules/datasource/multi-tenant-datasource.service.js +60 -183
  51. package/cjs/modules/utils/utils.service.js +27 -206
  52. package/cjs/utils/date-time.util.js +94 -0
  53. package/cjs/utils/html-sanitizer.util.js +64 -0
  54. package/cjs/utils/index.js +5 -1
  55. package/cjs/utils/query-helpers.util.js +60 -0
  56. package/cjs/utils/request.util.js +96 -0
  57. package/cjs/utils/string.util.js +63 -0
  58. package/classes/api-controller.class.d.ts +16 -8
  59. package/classes/api-service.class.d.ts +30 -25
  60. package/classes/handle-custome-db.d.ts +3 -0
  61. package/classes/index.d.ts +2 -2
  62. package/classes/winston.logger.class.d.ts +1 -0
  63. package/constants/index.d.ts +2 -1
  64. package/constants/message-keys.d.ts +45 -0
  65. package/constants/permissions.d.ts +306 -0
  66. package/decorators/api-response.decorator.d.ts +1 -1
  67. package/decorators/current-user.decorator.d.ts +1 -1
  68. package/decorators/index.d.ts +1 -0
  69. package/decorators/log-action.decorator.d.ts +8 -0
  70. package/decorators/require-permission.decorator.d.ts +2 -2
  71. package/dtos/delete.dto.d.ts +1 -0
  72. package/dtos/filter-and-pagination.dto.d.ts +0 -2
  73. package/dtos/get-by-ids.dto.d.ts +4 -0
  74. package/dtos/index.d.ts +1 -0
  75. package/dtos/response-payload.dto.d.ts +8 -20
  76. package/entities/index.d.ts +1 -0
  77. package/entities/raw-type.d.ts +1 -0
  78. package/enums/index.d.ts +3 -0
  79. package/enums/notification-type.enum.d.ts +6 -0
  80. package/enums/participant-status.enum.d.ts +6 -0
  81. package/enums/recurrence-type.enum.d.ts +7 -0
  82. package/exceptions/base-app.exception.d.ts +41 -0
  83. package/exceptions/index.d.ts +1 -0
  84. package/exceptions/permission.exception.d.ts +2 -2
  85. package/fesm/classes/api-controller.class.js +326 -50
  86. package/fesm/classes/api-service.class.js +274 -152
  87. package/fesm/classes/handle-custome-db.js +19 -0
  88. package/fesm/classes/index.js +3 -2
  89. package/fesm/classes/winston.logger.class.js +100 -68
  90. package/fesm/constants/index.js +4 -1
  91. package/fesm/constants/message-keys.js +49 -0
  92. package/fesm/constants/permissions.js +200 -0
  93. package/fesm/decorators/api-response.decorator.js +3 -21
  94. package/fesm/decorators/index.js +1 -0
  95. package/fesm/decorators/log-action.decorator.js +139 -0
  96. package/fesm/decorators/require-permission.decorator.js +17 -65
  97. package/fesm/dtos/delete.dto.js +12 -2
  98. package/fesm/dtos/filter-and-pagination.dto.js +26 -47
  99. package/fesm/dtos/get-by-ids.dto.js +55 -0
  100. package/fesm/dtos/identity-response.dto.js +4 -8
  101. package/fesm/dtos/index.js +1 -0
  102. package/fesm/dtos/pagination.dto.js +4 -8
  103. package/fesm/dtos/response-payload.dto.js +68 -103
  104. package/fesm/entities/identity.js +12 -6
  105. package/fesm/entities/index.js +1 -0
  106. package/fesm/entities/raw-type.js +1 -0
  107. package/fesm/entities/user-root.js +15 -15
  108. package/fesm/enums/index.js +3 -0
  109. package/fesm/enums/notification-type.enum.js +7 -0
  110. package/fesm/enums/participant-status.enum.js +7 -0
  111. package/fesm/enums/recurrence-type.enum.js +8 -0
  112. package/fesm/exceptions/base-app.exception.js +115 -0
  113. package/fesm/exceptions/index.js +1 -0
  114. package/fesm/exceptions/permission.exception.js +16 -18
  115. package/fesm/filters/global-exception.filter.js +269 -0
  116. package/fesm/filters/index.js +1 -0
  117. package/fesm/guards/jwt-auth.guard.js +5 -2
  118. package/fesm/guards/permission.guard.js +90 -150
  119. package/fesm/index.js +2 -0
  120. package/fesm/interceptors/idempotency.interceptor.js +2 -2
  121. package/fesm/interceptors/index.js +1 -4
  122. package/fesm/interceptors/set-user-field-on-body.interceptor.js +39 -0
  123. package/fesm/interceptors/slug.interceptor.js +31 -10
  124. package/fesm/interfaces/datasource.interface.js +1 -0
  125. package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
  126. package/fesm/interfaces/index.js +4 -2
  127. package/fesm/interfaces/module-config.interface.js +5 -0
  128. package/fesm/interfaces/notification-adapter.interface.js +1 -0
  129. package/fesm/interfaces/permission.interface.js +1 -3
  130. package/fesm/middlewares/logger.middleware.js +133 -109
  131. package/fesm/modules/cache/cache.module.js +2 -2
  132. package/fesm/modules/datasource/index.js +0 -1
  133. package/fesm/modules/datasource/multi-tenant-datasource.service.js +61 -184
  134. package/fesm/modules/utils/utils.service.js +27 -206
  135. package/fesm/utils/date-time.util.js +70 -0
  136. package/fesm/utils/html-sanitizer.util.js +69 -0
  137. package/fesm/utils/index.js +5 -1
  138. package/fesm/utils/query-helpers.util.js +85 -0
  139. package/fesm/utils/request.util.js +81 -0
  140. package/fesm/utils/string.util.js +71 -0
  141. package/filters/global-exception.filter.d.ts +9 -0
  142. package/filters/index.d.ts +1 -0
  143. package/guards/permission.guard.d.ts +4 -6
  144. package/index.d.ts +2 -0
  145. package/interceptors/idempotency.interceptor.d.ts +1 -1
  146. package/interceptors/index.d.ts +1 -4
  147. package/interceptors/set-user-field-on-body.interceptor.d.ts +5 -0
  148. package/interceptors/slug.interceptor.d.ts +2 -1
  149. package/interfaces/api.interface.d.ts +4 -2
  150. package/interfaces/datasource.interface.d.ts +5 -0
  151. package/interfaces/event-manager-adapter.interface.d.ts +43 -0
  152. package/interfaces/identity.interface.d.ts +4 -4
  153. package/interfaces/index.d.ts +4 -2
  154. package/interfaces/logged-user-info.interface.d.ts +0 -2
  155. package/interfaces/module-config.interface.d.ts +6 -0
  156. package/interfaces/notification-adapter.interface.d.ts +21 -0
  157. package/interfaces/permission.interface.d.ts +13 -9
  158. package/middlewares/logger.middleware.d.ts +2 -2
  159. package/modules/datasource/index.d.ts +0 -1
  160. package/modules/datasource/multi-tenant-datasource.service.d.ts +5 -16
  161. package/modules/utils/utils.service.d.ts +3 -14
  162. package/package.json +21 -14
  163. package/utils/date-time.util.d.ts +8 -0
  164. package/utils/html-sanitizer.util.d.ts +2 -0
  165. package/utils/index.d.ts +5 -1
  166. package/utils/query-helpers.util.d.ts +16 -0
  167. package/utils/request.util.d.ts +6 -0
  168. package/utils/string.util.d.ts +2 -0
  169. package/cjs/classes/request-scoped-api.service.js +0 -61
  170. package/cjs/classes/winston-logger-adapter.class.js +0 -121
  171. package/cjs/interceptors/query-performance.interceptor.js +0 -66
  172. package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -40
  173. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -40
  174. package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -40
  175. package/cjs/interfaces/base-query.interface.js +0 -6
  176. package/cjs/interfaces/logger.interface.js +0 -7
  177. package/cjs/modules/datasource/datasource.module.js +0 -120
  178. package/cjs/utils/error-handler.util.js +0 -67
  179. package/classes/request-scoped-api.service.d.ts +0 -13
  180. package/classes/winston-logger-adapter.class.d.ts +0 -20
  181. package/fesm/classes/request-scoped-api.service.js +0 -51
  182. package/fesm/classes/winston-logger-adapter.class.js +0 -124
  183. package/fesm/interceptors/query-performance.interceptor.js +0 -56
  184. package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -30
  185. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -30
  186. package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -30
  187. package/fesm/interfaces/base-query.interface.js +0 -3
  188. package/fesm/interfaces/logger.interface.js +0 -4
  189. package/fesm/modules/datasource/datasource.module.js +0 -110
  190. package/fesm/utils/error-handler.util.js +0 -59
  191. package/interceptors/query-performance.interceptor.d.ts +0 -8
  192. package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -5
  193. package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -5
  194. package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -5
  195. package/interfaces/base-query.interface.d.ts +0 -7
  196. package/interfaces/logger.interface.d.ts +0 -7
  197. package/modules/datasource/datasource.module.d.ts +0 -18
  198. package/utils/error-handler.util.d.ts +0 -24
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "MultiTenantDataSourceService", {
10
10
  });
11
11
  const _nestjscore = require("@flusys/nestjs-core");
12
12
  const _common = require("@nestjs/common");
13
+ const _constants = require("../../constants");
13
14
  const _core = require("@nestjs/core");
14
15
  const _express = require("express");
15
16
  const _typeorm = require("typeorm");
@@ -42,237 +43,128 @@ function _ts_param(paramIndex, decorator) {
42
43
  };
43
44
  }
44
45
  let MultiTenantDataSourceService = class MultiTenantDataSourceService {
45
- // Initialization
46
+ get self() {
47
+ return this.constructor;
48
+ }
46
49
  initializeFromOptions() {
47
50
  if (!this.options) return;
48
- if (!MultiTenantDataSourceService.initialized) {
51
+ if (!this.self.initialized) {
49
52
  this.registerTenants();
50
- MultiTenantDataSourceService.initialized = true;
53
+ this.self.initialized = true;
51
54
  }
52
55
  }
53
56
  registerTenants() {
54
57
  this.options?.tenants?.forEach((tenant)=>{
55
- MultiTenantDataSourceService.tenantsRegistry.set(tenant.id, tenant);
58
+ this.self.tenantsRegistry.set(tenant.id, tenant);
56
59
  });
57
60
  }
58
- // Public API
59
- /**
60
- * Set custom tenant header name
61
- */ setTenantHeader(header) {
62
- this.tenantHeader = header;
63
- }
64
- /**
65
- * Get current database mode
66
- */ getDatabaseMode() {
61
+ getDatabaseMode() {
67
62
  return this.options?.bootstrapAppConfig?.databaseMode ?? 'single';
68
63
  }
69
- /**
70
- * Check if running in multi-tenant mode
71
- */ isMultiTenant() {
64
+ isMultiTenant() {
72
65
  return this.getDatabaseMode() === 'multi-tenant';
73
66
  }
74
- // Tenant Resolution
75
- /**
76
- * Get current tenant ID from request header
77
- * Validates tenant ID format for security (alphanumeric, hyphens, underscores only)
78
- */ getCurrentTenantId() {
67
+ getCurrentTenantId() {
79
68
  if (!this.request) return null;
80
69
  const tenantId = this.request.headers[this.tenantHeader];
81
70
  if (!tenantId) return null;
82
- // Validate tenant ID format to prevent injection attacks
83
71
  if (!/^[a-zA-Z0-9_-]+$/.test(tenantId)) {
84
- throw new _common.BadRequestException('Invalid tenant ID format. Only alphanumeric characters, hyphens, and underscores are allowed.');
72
+ throw new _common.BadRequestException({
73
+ message: 'Invalid tenant ID format',
74
+ messageKey: _constants.SYSTEM_MESSAGES.INVALID_TENANT_ID
75
+ });
85
76
  }
86
77
  return tenantId;
87
78
  }
88
- /**
89
- * Get current tenant config from request header
90
- */ getCurrentTenant() {
79
+ getCurrentTenant() {
91
80
  const tenantId = this.getCurrentTenantId();
92
81
  return tenantId ? this.getTenant(tenantId) : null;
93
82
  }
94
- /**
95
- * Get tenant config by ID
96
- */ getTenant(tenantId) {
97
- return MultiTenantDataSourceService.tenantsRegistry.get(tenantId) ?? null;
98
- }
99
- /**
100
- * Get all registered tenants
101
- */ getAllTenants() {
102
- return Array.from(MultiTenantDataSourceService.tenantsRegistry.values());
83
+ getTenant(tenantId) {
84
+ return this.self.tenantsRegistry.get(tenantId) ?? null;
103
85
  }
104
- /**
105
- * Get only active tenants
106
- */ getActiveTenants() {
107
- return this.getAllTenants();
108
- }
109
- // DataSource Access
110
- /**
111
- * Get DataSource for current context (tenant or single)
112
- */ async getDataSource() {
86
+ async getDataSource() {
113
87
  return this.isMultiTenant() ? this.getTenantDataSource() : this.getSingleDataSource();
114
88
  }
115
- /**
116
- * Get DataSource for specific tenant
117
- */ async getDataSourceForTenant(tenantId) {
118
- const tenant = this.getTenant(tenantId);
119
- if (!tenant) {
120
- throw new Error(`Tenant '${tenantId}' not found`);
121
- }
122
- return this.getOrCreateTenantConnection(tenant);
123
- }
124
- /**
125
- * Set external DataSource (for single-tenant mode)
126
- */ setDataSource(dataSource) {
127
- MultiTenantDataSourceService.singleDataSource = dataSource;
128
- }
129
- // Repository Access
130
- /**
131
- * Get repository for entity in current context
132
- */ async getRepository(entity) {
89
+ async getRepository(entity) {
133
90
  const dataSource = await this.getDataSource();
134
91
  return dataSource.getRepository(entity);
135
92
  }
136
- /**
137
- * Get repository for entity in specific tenant
138
- */ async getRepositoryForTenant(entity, tenantId) {
139
- const dataSource = await this.getDataSourceForTenant(tenantId);
140
- return dataSource.getRepository(entity);
141
- }
142
- // Multi-Tenant Operations
143
- /**
144
- * Execute callback with specific tenant's DataSource
145
- */ async withTenant(tenantId, callback) {
146
- const dataSource = await this.getDataSourceForTenant(tenantId);
147
- return callback(dataSource);
148
- }
149
- /**
150
- * Execute callback for all active tenants
151
- */ async forAllTenants(callback) {
152
- const results = new Map();
153
- for (const tenant of this.getActiveTenants()){
154
- try {
155
- const dataSource = await this.getOrCreateTenantConnection(tenant);
156
- results.set(tenant.id, await callback(tenant, dataSource));
157
- } catch (error) {
158
- this.logger.error(`Error processing tenant ${tenant.id}: ${error}`);
159
- }
160
- }
161
- return results;
162
- }
163
- // Tenant Management
164
- /**
165
- * Register a new tenant at runtime
166
- */ registerTenant(tenant) {
167
- MultiTenantDataSourceService.tenantsRegistry.set(tenant.id, tenant);
168
- }
169
- /**
170
- * Remove tenant and close its connection
171
- */ async removeTenant(tenantId) {
172
- await this.closeTenantConnection(tenantId);
173
- MultiTenantDataSourceService.tenantsRegistry.delete(tenantId);
174
- }
175
- // Connection Lifecycle
176
- /**
177
- * Close specific tenant connection
178
- */ async closeTenantConnection(tenantId) {
179
- const connection = MultiTenantDataSourceService.tenantConnections.get(tenantId);
93
+ async closeTenantConnection(tenantId) {
94
+ const connection = this.self.tenantConnections.get(tenantId);
180
95
  if (connection?.isInitialized) {
181
96
  await connection.destroy();
182
- MultiTenantDataSourceService.tenantConnections.delete(tenantId);
183
- this.logger.log(`Closed connection for tenant: ${tenantId}`);
97
+ this.self.tenantConnections.delete(tenantId);
184
98
  }
185
99
  }
186
- /**
187
- * Lifecycle hook - cleanup on module destroy
188
- */ async onModuleDestroy() {
189
- for (const [tenantId] of MultiTenantDataSourceService.tenantConnections){
100
+ async onModuleDestroy() {
101
+ for (const [tenantId] of this.self.tenantConnections){
190
102
  await this.closeTenantConnection(tenantId);
191
103
  }
192
104
  }
193
- /**
194
- * Reset all static state (useful for testing)
195
- */ static reset() {
196
- MultiTenantDataSourceService.initialized = false;
197
- MultiTenantDataSourceService.singleDataSource = null;
198
- MultiTenantDataSourceService.singleConnectionLock = null;
199
- MultiTenantDataSourceService.tenantConnections.clear();
200
- MultiTenantDataSourceService.tenantsRegistry.clear();
201
- MultiTenantDataSourceService.connectionLocks.clear();
202
- }
203
- // Protected Methods (for subclasses)
204
- /**
205
- * Get single database DataSource with connection locking to prevent race conditions
206
- */ async getSingleDataSource() {
207
- // Return existing initialized connection
208
- if (MultiTenantDataSourceService.singleDataSource?.isInitialized) {
209
- return MultiTenantDataSourceService.singleDataSource;
105
+ async getSingleDataSource() {
106
+ if (this.self.singleDataSource?.isInitialized) {
107
+ return this.self.singleDataSource;
210
108
  }
211
- // If another request is creating the connection, wait for it
212
- if (MultiTenantDataSourceService.singleConnectionLock) {
213
- return MultiTenantDataSourceService.singleConnectionLock;
109
+ if (this.self.singleConnectionLock) {
110
+ return this.self.singleConnectionLock;
214
111
  }
215
112
  const config = this.getDefaultDatabaseConfig();
216
113
  if (!config) {
217
- throw new Error('No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.');
114
+ throw new _common.InternalServerErrorException({
115
+ message: 'No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.',
116
+ messageKey: _constants.SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
117
+ });
218
118
  }
219
- // Create connection with lock to prevent race conditions
220
119
  const connectionPromise = this.createDataSourceFromConfig(config);
221
- MultiTenantDataSourceService.singleConnectionLock = connectionPromise;
120
+ this.self.singleConnectionLock = connectionPromise;
222
121
  try {
223
122
  const dataSource = await connectionPromise;
224
- MultiTenantDataSourceService.singleDataSource = dataSource;
123
+ this.self.singleDataSource = dataSource;
225
124
  return dataSource;
226
125
  } finally{
227
- MultiTenantDataSourceService.singleConnectionLock = null;
126
+ this.self.singleConnectionLock = null;
228
127
  }
229
128
  }
230
- /**
231
- * Get tenant-specific DataSource
232
- */ async getTenantDataSource() {
129
+ async getTenantDataSource() {
233
130
  const tenant = this.getCurrentTenant();
234
131
  if (!tenant) {
235
- throw new Error(`Tenant not found. Ensure '${this.tenantHeader}' header is set.`);
132
+ throw new _common.BadRequestException({
133
+ message: `Tenant not found. Ensure '${this.tenantHeader}' header is set.`,
134
+ messageKey: _constants.SYSTEM_MESSAGES.TENANT_HEADER_REQUIRED,
135
+ messageVariables: {
136
+ header: this.tenantHeader
137
+ }
138
+ });
236
139
  }
237
140
  return this.getOrCreateTenantConnection(tenant);
238
141
  }
239
- /**
240
- * Get or create connection for tenant with locking to prevent race conditions
241
- */ async getOrCreateTenantConnection(tenant) {
242
- // Return existing initialized connection
243
- const existing = MultiTenantDataSourceService.tenantConnections.get(tenant.id);
244
- if (existing?.isInitialized) {
245
- return existing;
246
- }
247
- // If another request is creating this tenant's connection, wait for it
248
- const pendingConnection = MultiTenantDataSourceService.connectionLocks.get(tenant.id);
249
- if (pendingConnection) {
250
- return pendingConnection;
251
- }
252
- // Create connection with lock to prevent race conditions
142
+ async getOrCreateTenantConnection(tenant) {
143
+ const existing = this.self.tenantConnections.get(tenant.id);
144
+ if (existing?.isInitialized) return existing;
145
+ const pendingConnection = this.self.connectionLocks.get(tenant.id);
146
+ if (pendingConnection) return pendingConnection;
253
147
  const config = this.buildTenantDatabaseConfig(tenant);
254
148
  const connectionPromise = this.createDataSourceFromConfig(config);
255
- MultiTenantDataSourceService.connectionLocks.set(tenant.id, connectionPromise);
149
+ this.self.connectionLocks.set(tenant.id, connectionPromise);
256
150
  try {
257
151
  const dataSource = await connectionPromise;
258
- MultiTenantDataSourceService.tenantConnections.set(tenant.id, dataSource);
259
- this.logger.log(`Created connection for tenant: ${tenant.id}`);
152
+ this.self.tenantConnections.set(tenant.id, dataSource);
260
153
  return dataSource;
261
154
  } finally{
262
- MultiTenantDataSourceService.connectionLocks.delete(tenant.id);
155
+ this.self.connectionLocks.delete(tenant.id);
263
156
  }
264
157
  }
265
- /**
266
- * Get default database config (supports both naming conventions)
267
- */ getDefaultDatabaseConfig() {
158
+ getDefaultDatabaseConfig() {
268
159
  return this.options?.defaultDatabaseConfig ?? this.options?.tenantDefaultDatabaseConfig;
269
160
  }
270
- /**
271
- * Build database config for tenant (merges with default)
272
- */ buildTenantDatabaseConfig(tenant) {
161
+ buildTenantDatabaseConfig(tenant) {
273
162
  const defaultConfig = this.getDefaultDatabaseConfig();
274
163
  if (!defaultConfig) {
275
- throw new Error('No default database config for multi-tenant mode.');
164
+ throw new _common.InternalServerErrorException({
165
+ message: 'No default database config for multi-tenant mode.',
166
+ messageKey: _constants.SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
167
+ });
276
168
  }
277
169
  return {
278
170
  type: defaultConfig.type,
@@ -283,15 +175,7 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
283
175
  database: tenant.database
284
176
  };
285
177
  }
286
- /**
287
- * Create DataSource from config - override in subclasses for custom logic
288
- *
289
- * Note: In multi-tenant mode, subclasses should pass entities array.
290
- * For single-tenant mode, entities are registered via TypeOrmModule at app level.
291
- *
292
- * @param config - Database configuration
293
- * @param entities - Optional entities array (for multi-tenant mode)
294
- */ async createDataSourceFromConfig(config, entities = []) {
178
+ async createDataSourceFromConfig(config, entities = []) {
295
179
  const dataSource = new _typeorm.DataSource({
296
180
  type: config.type,
297
181
  host: config.host,
@@ -303,30 +187,23 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
303
187
  synchronize: false,
304
188
  namingStrategy: new _typeormnamingstrategies.SnakeNamingStrategy()
305
189
  });
306
- if (!dataSource.isInitialized) {
307
- await dataSource.initialize();
308
- }
190
+ if (!dataSource.isInitialized) await dataSource.initialize();
309
191
  return dataSource;
310
192
  }
311
193
  constructor(options, request){
312
194
  _define_property(this, "options", void 0);
313
195
  _define_property(this, "request", void 0);
314
- _define_property(this, "logger", void 0);
315
- // Instance state
316
196
  _define_property(this, "tenantHeader", void 0);
317
197
  this.options = options;
318
198
  this.request = request;
319
- this.logger = new _common.Logger(this.constructor.name);
320
199
  this.tenantHeader = _nestjscore.DEFAULT_TENANT_HEADER;
321
200
  this.initializeFromOptions();
322
201
  }
323
202
  };
324
- // Static state shared across all instances
325
203
  _define_property(MultiTenantDataSourceService, "tenantConnections", new Map());
326
204
  _define_property(MultiTenantDataSourceService, "singleDataSource", null);
327
205
  _define_property(MultiTenantDataSourceService, "tenantsRegistry", new Map());
328
206
  _define_property(MultiTenantDataSourceService, "initialized", false);
329
- // Connection locks to prevent race conditions during concurrent connection creation
330
207
  _define_property(MultiTenantDataSourceService, "connectionLocks", new Map());
331
208
  _define_property(MultiTenantDataSourceService, "singleConnectionLock", null);
332
209
  MultiTenantDataSourceService = _ts_decorate([
@@ -9,247 +9,68 @@ Object.defineProperty(exports, "UtilsService", {
9
9
  }
10
10
  });
11
11
  const _common = require("@nestjs/common");
12
- const _classvalidator = require("class-validator");
13
12
  function _ts_decorate(decorators, target, key, desc) {
14
13
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
15
14
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
16
15
  else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
17
16
  return c > 3 && r && Object.defineProperty(target, key, r), r;
18
17
  }
19
- function _ts_metadata(k, v) {
20
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
21
- }
22
18
  let UtilsService = class UtilsService {
23
19
  // ---------------- CACHE HELPERS ----------------
24
20
  /**
25
21
  * Generate cache key with optional tenant prefix for multi-tenant isolation
26
- * @param entityName - Name of the entity being cached
27
- * @param params - Query parameters for cache key uniqueness
28
- * @param entityId - Optional entity ID for single entity caching
29
- * @param tenantId - Optional tenant ID for multi-tenant isolation
30
22
  */ getCacheKey(entityName, params, entityId, tenantId) {
31
- const tenantPrefix = tenantId ? `tenant_${tenantId}_` : '';
32
- if (entityId) return `${tenantPrefix}entity_${entityName}_id_${entityId}${params ? '_select_' + JSON.stringify(params) : ''}`;
33
- return `${tenantPrefix}entity_${entityName}_all_${JSON.stringify(params)}`;
23
+ const prefix = this.buildTenantPrefix(tenantId);
24
+ if (entityId) {
25
+ return `${prefix}entity_${entityName}_id_${entityId}${params ? '_select_' + JSON.stringify(params) : ''}`;
26
+ }
27
+ return `${prefix}entity_${entityName}_all_${JSON.stringify(params)}`;
34
28
  }
35
29
  /**
36
30
  * Track cache key for later invalidation with optional tenant prefix
37
31
  */ async trackCacheKey(cacheKey, entityName, cacheManager, entityId, tenantId) {
38
- const tenantPrefix = tenantId ? `tenant_${tenantId}_` : '';
39
32
  try {
40
- if (entityId) {
41
- const trackingKey = `${tenantPrefix}entity_${entityName}_id_${entityId}_keys`;
42
- const idKeys = await cacheManager.get(trackingKey) || [];
43
- if (!idKeys.includes(cacheKey)) idKeys.push(cacheKey);
44
- await cacheManager.set(trackingKey, idKeys);
45
- } else {
46
- const trackingKey = `${tenantPrefix}entity_${entityName}_keys`;
47
- const allKeys = await cacheManager.get(trackingKey) || [];
48
- if (!allKeys.includes(cacheKey)) allKeys.push(cacheKey);
49
- await cacheManager.set(trackingKey, allKeys);
50
- }
51
- } catch (error) {
52
- // Log but don't throw - cache tracking failure shouldn't break the request
53
- console.error(`Cache tracking failed for ${entityName}:`, error);
33
+ const trackingKey = this.buildTrackingKey(entityName, entityId, tenantId);
34
+ const keys = await cacheManager.get(trackingKey) || [];
35
+ if (!keys.includes(cacheKey)) keys.push(cacheKey);
36
+ await cacheManager.set(trackingKey, keys);
37
+ } catch {
38
+ // Silent failure - cache tracking is non-critical
54
39
  }
55
40
  }
56
41
  /**
57
42
  * Clear cache for entity with optional tenant prefix
58
- * Uses Promise.allSettled to ensure all deletions are attempted even if some fail
59
43
  */ async clearCache(entityName, cacheManager, entityId, tenantId) {
60
- const tenantPrefix = tenantId ? `tenant_${tenantId}_` : '';
61
44
  try {
62
- if (entityId) {
63
- const trackingKey = `${tenantPrefix}entity_${entityName}_id_${entityId}_keys`;
64
- const idKeys = await cacheManager.get(trackingKey) || [];
65
- // Use Promise.allSettled to ensure all deletions are attempted
66
- await Promise.allSettled(idKeys.map((key)=>cacheManager.del(key)));
67
- await cacheManager.del(trackingKey);
68
- } else {
69
- const trackingKey = `${tenantPrefix}entity_${entityName}_keys`;
70
- const keySet = await cacheManager.get(trackingKey) || [];
71
- // Use Promise.allSettled to ensure all deletions are attempted
72
- await Promise.allSettled(keySet.map((key)=>cacheManager.del(key)));
73
- await cacheManager.del(trackingKey);
74
- }
75
- } catch (error) {
76
- // Log but don't throw - cache invalidation failure shouldn't break the request
77
- console.error(`Cache invalidation failed for ${entityName}:`, error);
78
- }
79
- }
80
- /**
81
- * Check Phone or Email
82
- */ checkPhoneOrEmail(value) {
83
- if ((0, _classvalidator.isEmail)(value)) {
84
- return {
85
- value: value,
86
- type: 'email'
87
- };
88
- }
89
- const phoneMatch = value.match(/^((\+880)|0)?(13|15|16|17|18|19)\d{8}$/);
90
- if (phoneMatch) {
91
- let phone = phoneMatch[0];
92
- if (!phone.startsWith('+88')) {
93
- phone = '+88' + phone;
94
- }
95
- return {
96
- value: phone,
97
- type: 'phone'
98
- };
45
+ const trackingKey = this.buildTrackingKey(entityName, entityId, tenantId);
46
+ const keys = await cacheManager.get(trackingKey) || [];
47
+ await Promise.allSettled(keys.map((key)=>cacheManager.del(key)));
48
+ await cacheManager.del(trackingKey);
49
+ } catch {
50
+ // Silent failure - cache invalidation is non-critical
99
51
  }
100
- return {
101
- value: null,
102
- type: null
103
- };
104
52
  }
53
+ // ---------------- STRING HELPERS ----------------
105
54
  /**
106
- * STRING FUNCTIONS
107
- * transformToSlug
55
+ * Transform string to URL-friendly slug
108
56
  */ transformToSlug(value, salt) {
109
57
  const slug = value?.trim().replace(/[^A-Z0-9]+/gi, '-').toLowerCase();
110
58
  return salt ? `${slug}-${this.getRandomInt(1, 100)}` : slug;
111
59
  }
112
60
  /**
113
- * RANDOM FUNCTIONS
114
- * getRandomInt
115
- * generateRandomId
116
- * getRandomOtpCode
61
+ * Generate random integer between min and max (inclusive)
117
62
  */ getRandomInt(min, max) {
118
63
  return Math.floor(Math.random() * (max - min + 1)) + min;
119
64
  }
120
- generateRandomId(length) {
121
- const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
122
- let result = '';
123
- for(let i = 0; i < length; i++){
124
- result += characters.charAt(Math.floor(Math.random() * characters.length));
125
- }
126
- return result;
127
- }
128
- getRandomOtpCode() {
129
- return Math.floor(Math.random() * (9999 - 1000 + 1)) + 1000;
130
- }
131
- extractColumnNameFromError(detail) {
132
- const match = detail.match(/\((.*?)\)=\((.*?)\)/);
133
- return {
134
- columnName: match ? match[1] : 'unknown',
135
- value: match ? match[2] : 'unknown'
136
- };
137
- }
138
- getOtpEmailFormat(otp, userName) {
139
- return `
140
- <!DOCTYPE html>
141
- <html lang="en">
142
- <head>
143
- <meta charset="UTF-8" />
144
- <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
145
- <title>Your OTP Code</title>
146
- <style>
147
- body {
148
- font-family: Arial, sans-serif;
149
- background-color: #f4f4f7;
150
- margin: 0;
151
- padding: 0;
152
- }
153
- .email-container {
154
- max-width: 500px;
155
- margin: 40px auto;
156
- background-color: #ffffff;
157
- padding: 30px;
158
- border-radius: 8px;
159
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
160
- text-align: center;
161
- }
162
- h1 {
163
- color: #333;
164
- }
165
- p {
166
- font-size: 16px;
167
- color: #555;
168
- }
169
- .otp-box {
170
- display: inline-block;
171
- margin: 20px 0;
172
- padding: 14px 28px;
173
- font-size: 24px;
174
- letter-spacing: 6px;
175
- background-color: #007BFF;
176
- color: white;
177
- border-radius: 8px;
178
- font-weight: bold;
179
- }
180
- </style>
181
- </head>
182
- <body>
183
- <div class="email-container">
184
- <p>Hi ${userName || 'Sir/Madam'},</p>
185
- <p>Use the code below to verify your identity:</p>
186
- <div class="otp-box">${otp}</div>
187
- <p>This OTP is valid for a limited time. Do not share it with anyone.</p>
188
- <p>If you didn’t request this, please ignore this email.</p>
189
- </div>
190
- </body>
191
- </html>
192
- `;
65
+ // ---------------- PRIVATE HELPERS ----------------
66
+ buildTenantPrefix(tenantId) {
67
+ return tenantId ? `tenant_${tenantId}_` : '';
193
68
  }
194
- getResetPasswordEmailFormat(resetLink, userName) {
195
- return `
196
- <!DOCTYPE html>
197
- <html lang="en">
198
- <head>
199
- <meta charset="UTF-8" />
200
- <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
201
- <title>Reset Your Password</title>
202
- <style>
203
- body {
204
- font-family: Arial, sans-serif;
205
- background-color: #f4f4f7;
206
- margin: 0;
207
- padding: 0;
208
- }
209
- .email-container {
210
- max-width: 500px;
211
- margin: 40px auto;
212
- background-color: #ffffff;
213
- padding: 30px;
214
- border-radius: 8px;
215
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
216
- text-align: center;
217
- }
218
- h1 {
219
- color: #333;
220
- }
221
- p {
222
- font-size: 16px;
223
- color: #555;
224
- }
225
- .reset-button {
226
- display: inline-block;
227
- margin: 20px 0;
228
- padding: 14px 28px;
229
- font-size: 16px;
230
- background-color: #007BFF;
231
- color: #ffffff !important;
232
- text-decoration: none;
233
- border-radius: 6px;
234
- font-weight: bold;
235
- }
236
- </style>
237
- </head>
238
- <body>
239
- <div class="email-container">
240
- <p>Hi ${userName || 'Sir/Madam'},</p>
241
- <p>We received a request to reset your password. Click the button below to reset it:</p>
242
- <a href="${resetLink}" class="reset-button" target="_blank">Reset Password</a>
243
- <p>If you didn’t request a password reset, you can safely ignore this email.</p>
244
- </div>
245
- </body>
246
- </html>
247
- `;
69
+ buildTrackingKey(entityName, entityId, tenantId) {
70
+ const prefix = this.buildTenantPrefix(tenantId);
71
+ return entityId ? `${prefix}entity_${entityName}_id_${entityId}_keys` : `${prefix}entity_${entityName}_keys`;
248
72
  }
249
- constructor(){}
250
73
  };
251
74
  UtilsService = _ts_decorate([
252
- (0, _common.Injectable)(),
253
- _ts_metadata("design:type", Function),
254
- _ts_metadata("design:paramtypes", [])
75
+ (0, _common.Injectable)()
255
76
  ], UtilsService);