@flusys/nestjs-shared 1.0.0-beta → 1.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.
Files changed (113) hide show
  1. package/README.md +501 -720
  2. package/cjs/classes/api-controller.class.js +9 -24
  3. package/cjs/classes/api-service.class.js +59 -92
  4. package/cjs/classes/index.js +1 -0
  5. package/cjs/classes/winston-logger-adapter.class.js +23 -40
  6. package/cjs/constants/index.js +14 -0
  7. package/cjs/constants/permissions.js +184 -0
  8. package/cjs/decorators/api-response.decorator.js +1 -1
  9. package/cjs/decorators/index.js +1 -0
  10. package/cjs/decorators/sanitize-html.decorator.js +36 -0
  11. package/cjs/dtos/delete.dto.js +10 -0
  12. package/cjs/dtos/filter-and-pagination.dto.js +24 -34
  13. package/cjs/dtos/pagination.dto.js +4 -8
  14. package/cjs/dtos/response-payload.dto.js +0 -116
  15. package/cjs/entities/identity.js +4 -4
  16. package/cjs/entities/user-root.js +13 -14
  17. package/cjs/guards/permission.guard.js +51 -105
  18. package/cjs/interceptors/index.js +1 -3
  19. package/cjs/interceptors/set-user-field-on-body.interceptor.js +60 -0
  20. package/cjs/interceptors/slug.interceptor.js +30 -9
  21. package/cjs/interfaces/datasource.interface.js +4 -0
  22. package/cjs/interfaces/index.js +2 -1
  23. package/cjs/interfaces/module-config.interface.js +4 -0
  24. package/cjs/middlewares/logger.middleware.js +50 -89
  25. package/cjs/modules/cache/cache.module.js +3 -3
  26. package/cjs/modules/datasource/datasource.module.js +11 -14
  27. package/cjs/modules/datasource/multi-tenant-datasource.service.js +29 -113
  28. package/cjs/modules/utils/utils.service.js +40 -203
  29. package/cjs/utils/error-handler.util.js +35 -12
  30. package/cjs/utils/html-sanitizer.util.js +64 -0
  31. package/cjs/utils/index.js +4 -0
  32. package/cjs/utils/query-helpers.util.js +53 -0
  33. package/cjs/utils/request.util.js +70 -0
  34. package/cjs/utils/string.util.js +63 -0
  35. package/classes/api-controller.class.d.ts +5 -5
  36. package/classes/api-service.class.d.ts +7 -5
  37. package/classes/index.d.ts +1 -0
  38. package/classes/request-scoped-api.service.d.ts +3 -2
  39. package/classes/winston-logger-adapter.class.d.ts +2 -0
  40. package/constants/index.d.ts +1 -0
  41. package/constants/permissions.d.ts +179 -0
  42. package/decorators/index.d.ts +1 -0
  43. package/decorators/sanitize-html.decorator.d.ts +2 -0
  44. package/dtos/delete.dto.d.ts +1 -0
  45. package/dtos/filter-and-pagination.dto.d.ts +0 -2
  46. package/dtos/response-payload.dto.d.ts +0 -20
  47. package/fesm/classes/api-controller.class.js +9 -24
  48. package/fesm/classes/api-service.class.js +59 -92
  49. package/fesm/classes/index.js +2 -0
  50. package/fesm/classes/winston-logger-adapter.class.js +23 -40
  51. package/fesm/constants/index.js +2 -0
  52. package/fesm/constants/permissions.js +128 -0
  53. package/fesm/decorators/api-response.decorator.js +1 -1
  54. package/fesm/decorators/index.js +1 -0
  55. package/fesm/decorators/sanitize-html.decorator.js +45 -0
  56. package/fesm/dtos/delete.dto.js +12 -2
  57. package/fesm/dtos/filter-and-pagination.dto.js +26 -47
  58. package/fesm/dtos/pagination.dto.js +4 -8
  59. package/fesm/dtos/response-payload.dto.js +0 -107
  60. package/fesm/entities/identity.js +4 -4
  61. package/fesm/entities/user-root.js +13 -14
  62. package/fesm/guards/permission.guard.js +51 -105
  63. package/fesm/interceptors/index.js +1 -3
  64. package/fesm/interceptors/set-user-field-on-body.interceptor.js +39 -0
  65. package/fesm/interceptors/slug.interceptor.js +31 -10
  66. package/fesm/interfaces/datasource.interface.js +20 -0
  67. package/fesm/interfaces/index.js +2 -1
  68. package/fesm/interfaces/module-config.interface.js +5 -0
  69. package/fesm/middlewares/logger.middleware.js +50 -83
  70. package/fesm/modules/cache/cache.module.js +2 -2
  71. package/fesm/modules/datasource/datasource.module.js +11 -14
  72. package/fesm/modules/datasource/multi-tenant-datasource.service.js +29 -113
  73. package/fesm/modules/utils/utils.service.js +41 -204
  74. package/fesm/utils/error-handler.util.js +36 -13
  75. package/fesm/utils/html-sanitizer.util.js +69 -0
  76. package/fesm/utils/index.js +4 -0
  77. package/fesm/utils/query-helpers.util.js +78 -0
  78. package/fesm/utils/request.util.js +58 -0
  79. package/fesm/utils/string.util.js +71 -0
  80. package/guards/permission.guard.d.ts +2 -0
  81. package/interceptors/index.d.ts +1 -3
  82. package/interceptors/set-user-field-on-body.interceptor.d.ts +5 -0
  83. package/interceptors/slug.interceptor.d.ts +2 -1
  84. package/interfaces/api.interface.d.ts +2 -2
  85. package/interfaces/datasource.interface.d.ts +5 -0
  86. package/interfaces/identity.interface.d.ts +4 -4
  87. package/interfaces/index.d.ts +2 -1
  88. package/interfaces/logged-user-info.interface.d.ts +0 -2
  89. package/interfaces/module-config.interface.d.ts +6 -0
  90. package/interfaces/permission.interface.d.ts +0 -1
  91. package/middlewares/logger.middleware.d.ts +2 -2
  92. package/modules/datasource/datasource.module.d.ts +1 -0
  93. package/modules/datasource/multi-tenant-datasource.service.d.ts +0 -1
  94. package/modules/utils/utils.service.d.ts +4 -14
  95. package/package.json +4 -4
  96. package/utils/error-handler.util.d.ts +14 -19
  97. package/utils/html-sanitizer.util.d.ts +2 -0
  98. package/utils/index.d.ts +4 -0
  99. package/utils/query-helpers.util.d.ts +16 -0
  100. package/utils/request.util.d.ts +4 -0
  101. package/utils/string.util.d.ts +2 -0
  102. package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -40
  103. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -40
  104. package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -40
  105. package/cjs/interfaces/base-query.interface.js +0 -6
  106. package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -30
  107. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -30
  108. package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -30
  109. package/fesm/interfaces/base-query.interface.js +0 -3
  110. package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -5
  111. package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -5
  112. package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -5
  113. package/interfaces/base-query.interface.d.ts +0 -7
@@ -29,26 +29,47 @@ function _ts_decorate(decorators, target, key, desc) {
29
29
  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;
30
30
  return c > 3 && r && Object.defineProperty(target, key, r), r;
31
31
  }
32
+ function _ts_metadata(k, v) {
33
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
34
+ }
35
+ function _ts_param(paramIndex, decorator) {
36
+ return function(target, key) {
37
+ decorator(target, key, paramIndex);
38
+ };
39
+ }
32
40
  let Slug = class Slug {
33
41
  intercept(context, next) {
34
42
  const request = context.switchToHttp().getRequest();
35
43
  if (Array.isArray(request.body)) {
36
- request.body = request.body.map((item)=>({
37
- ...item,
38
- slug: item.slug || (item?.name ? this.utilsService.transformToSlug(item.name) : null)
39
- }));
40
- } else if (!request.body.slug) {
44
+ request.body = request.body.map((item)=>{
45
+ // Only generate slug if not provided and name exists
46
+ if (!item.slug && item?.name) {
47
+ return {
48
+ ...item,
49
+ slug: this.utilsService.transformToSlug(item.name)
50
+ };
51
+ }
52
+ return item;
53
+ });
54
+ } else if (!request.body?.slug && request.body?.name) {
55
+ // Only generate slug if not provided and name exists
41
56
  request.body = {
42
57
  ...request.body,
43
- slug: request.body?.name ? this.utilsService.transformToSlug(request.body?.name) : null
58
+ slug: this.utilsService.transformToSlug(request.body.name)
44
59
  };
45
60
  }
46
61
  return next.handle();
47
62
  }
48
- constructor(){
49
- _define_property(this, "utilsService", new _utilsservice.UtilsService());
63
+ constructor(utilsService){
64
+ _define_property(this, "utilsService", void 0);
65
+ this.utilsService = utilsService;
50
66
  }
51
67
  };
52
68
  Slug = _ts_decorate([
53
- (0, _common.Injectable)()
69
+ (0, _common.Injectable)(),
70
+ _ts_param(0, (0, _common.Inject)(_utilsservice.UtilsService)),
71
+ _ts_metadata("design:type", Function),
72
+ _ts_metadata("design:paramtypes", [
73
+ typeof _utilsservice.UtilsService === "undefined" ? Object : _utilsservice.UtilsService
74
+ ])
54
75
  ], Slug);
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
5
  _export_star(require("./api.interface"), exports);
6
- _export_star(require("./base-query.interface"), exports);
6
+ _export_star(require("./datasource.interface"), exports);
7
7
  _export_star(require("./identity.interface"), exports);
8
8
  _export_star(require("./logged-user-info.interface"), exports);
9
9
  _export_star(require("./logger.interface"), exports);
10
+ _export_star(require("./module-config.interface"), exports);
10
11
  _export_star(require("./permission.interface"), exports);
11
12
  function _export_star(from, to) {
12
13
  Object.keys(from).forEach(function(k) {
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
@@ -26,12 +26,6 @@ _export(exports, {
26
26
  },
27
27
  get requestContext () {
28
28
  return requestContext;
29
- },
30
- get setCompanyId () {
31
- return setCompanyId;
32
- },
33
- get setUserId () {
34
- return setUserId;
35
29
  }
36
30
  });
37
31
  const _config = require("@flusys/nestjs-core/config");
@@ -78,14 +72,6 @@ const getRequestId = ()=>requestContext.getStore()?.requestId;
78
72
  const getTenantId = ()=>requestContext.getStore()?.tenantId;
79
73
  const getUserId = ()=>requestContext.getStore()?.userId;
80
74
  const getCompanyId = ()=>requestContext.getStore()?.companyId;
81
- const setUserId = (userId)=>{
82
- const store = requestContext.getStore();
83
- if (store) store.userId = userId;
84
- };
85
- const setCompanyId = (companyId)=>{
86
- const store = requestContext.getStore();
87
- if (store) store.companyId = companyId;
88
- };
89
75
  // Helper Functions
90
76
  function sanitizeHeaders(headers) {
91
77
  const sanitized = {};
@@ -107,84 +93,62 @@ let LoggerMiddleware = class LoggerMiddleware {
107
93
  const requestId = req.headers[_constants.REQUEST_ID_HEADER] || (0, _uuid.v4)();
108
94
  const tenantId = req.headers[TENANT_ID_HEADER];
109
95
  const startTime = Date.now();
110
- // Set response header
111
96
  res.setHeader(_constants.REQUEST_ID_HEADER, requestId);
112
- // Create context
113
97
  const context = {
114
98
  requestId,
115
99
  tenantId,
116
100
  startTime
117
101
  };
118
- // Run in AsyncLocalStorage context
119
102
  requestContext.run(context, ()=>{
120
- // Check if we should skip logging
121
103
  const shouldSkipLogging = EXCLUDED_PATHS.some((path)=>req.originalUrl.startsWith(path));
122
104
  if (!shouldSkipLogging) {
123
105
  this.logRequest(req, requestId, tenantId);
124
- // Track if response was already logged
125
- let responseLogged = false;
126
- // Capture response using multiple hooks to ensure we catch it
127
- const originalSend = res.send;
128
- const originalJson = res.json;
129
- const originalEnd = res.end;
130
- // Store reference to this for use in callbacks
131
- const self = this;
132
- // Override res.send
133
- res.send = function(body) {
134
- if (!responseLogged) {
135
- responseLogged = true;
136
- self.logResponse(req, res, startTime, body, requestId, tenantId);
137
- }
138
- return originalSend.call(this, body);
139
- };
140
- // Override res.json
141
- res.json = function(body) {
142
- if (!responseLogged) {
143
- responseLogged = true;
144
- self.logResponse(req, res, startTime, body, requestId, tenantId);
145
- }
146
- return originalJson.call(this, body);
147
- };
148
- // Override res.end as fallback
149
- res.end = function(...args) {
150
- if (!responseLogged) {
151
- responseLogged = true;
152
- self.logResponse(req, res, startTime, args[0], requestId, tenantId);
153
- }
154
- return originalEnd.apply(this, args);
155
- };
156
- // Handle errors
157
- res.on('error', (error)=>{
158
- this.logger.error('Response error', {
159
- context: 'HTTP',
160
- requestId,
161
- tenantId,
162
- method: req.method,
163
- url: req.originalUrl,
164
- path: req.path,
165
- error: error.message,
166
- stack: error.stack
167
- });
168
- });
106
+ this.setupResponseLogging(req, res, startTime, requestId, tenantId);
169
107
  }
170
108
  next();
171
109
  });
172
110
  }
111
+ setupResponseLogging(req, res, startTime, requestId, tenantId) {
112
+ let responseLogged = false;
113
+ const originalSend = res.send;
114
+ const originalJson = res.json;
115
+ const originalEnd = res.end;
116
+ const self = this;
117
+ const logOnce = (body)=>{
118
+ if (!responseLogged) {
119
+ responseLogged = true;
120
+ self.logResponse(req, res, startTime, body, requestId, tenantId);
121
+ }
122
+ };
123
+ res.send = function(body) {
124
+ logOnce(body);
125
+ return originalSend.call(this, body);
126
+ };
127
+ res.json = function(body) {
128
+ logOnce(body);
129
+ return originalJson.call(this, body);
130
+ };
131
+ res.end = function(...args) {
132
+ logOnce(args[0]);
133
+ return originalEnd.apply(this, args);
134
+ };
135
+ res.on('error', (error)=>{
136
+ this.logger.error('Response error', {
137
+ ...this.buildBaseLogData(req, requestId, tenantId),
138
+ error: error.message,
139
+ stack: error.stack
140
+ });
141
+ });
142
+ }
173
143
  logRequest(req, requestId, tenantId) {
174
144
  const logData = {
175
- context: 'HTTP',
176
- requestId,
177
- tenantId,
178
- method: req.method,
179
- url: req.originalUrl,
180
- path: req.path,
145
+ ...this.buildBaseLogData(req, requestId, tenantId),
181
146
  query: Object.keys(req.query).length > 0 ? req.query : undefined,
182
147
  ip: getClientIp(req),
183
148
  userAgent: req.headers['user-agent'],
184
149
  contentType: req.headers['content-type'],
185
150
  contentLength: req.headers['content-length']
186
151
  };
187
- // Add debug details if enabled
188
152
  if (IS_DEBUG) {
189
153
  logData.headers = sanitizeHeaders(req.headers);
190
154
  logData.body = truncateBody(req.body);
@@ -196,13 +160,12 @@ let LoggerMiddleware = class LoggerMiddleware {
196
160
  const duration = Date.now() - startTime;
197
161
  const statusCode = res.statusCode;
198
162
  const level = statusCode >= 500 ? 'error' : statusCode >= 400 ? 'warn' : 'info';
163
+ const userId = getUserId();
164
+ const companyId = getCompanyId();
199
165
  const logData = {
200
- context: 'HTTP',
201
- requestId,
202
- tenantId,
203
- method: req.method,
204
- url: req.originalUrl,
205
- path: req.path,
166
+ ...this.buildBaseLogData(req, requestId, tenantId),
167
+ userId,
168
+ companyId,
206
169
  statusCode,
207
170
  statusMessage: res.statusMessage,
208
171
  duration: `${duration}ms`,
@@ -210,33 +173,31 @@ let LoggerMiddleware = class LoggerMiddleware {
210
173
  contentType: res.getHeader('content-type'),
211
174
  contentLength: res.getHeader('content-length')
212
175
  };
213
- // Add user context if available
214
- const userId = getUserId();
215
- const companyId = getCompanyId();
216
- if (userId) logData.userId = userId;
217
- if (companyId) logData.companyId = companyId;
218
- // Add response body for errors or debug mode
219
176
  if (statusCode >= 400 || IS_DEBUG) {
220
177
  logData.responseBody = truncateBody(body);
221
178
  }
222
179
  this.logger.log(level, `Response [${statusCode}]`, logData);
223
- // Log slow requests separately
224
180
  if (duration > 3000 && statusCode < 400) {
225
181
  this.logger.warn('Slow request detected', {
226
- context: 'HTTP',
227
- requestId,
228
- tenantId,
182
+ ...this.buildBaseLogData(req, requestId, tenantId),
229
183
  userId,
230
184
  companyId,
231
- method: req.method,
232
- url: req.originalUrl,
233
- path: req.path,
234
185
  duration: `${duration}ms`,
235
186
  durationMs: duration,
236
187
  threshold: '3000ms'
237
188
  });
238
189
  }
239
190
  }
191
+ buildBaseLogData(req, requestId, tenantId) {
192
+ return {
193
+ context: 'HTTP',
194
+ requestId,
195
+ tenantId,
196
+ method: req.method,
197
+ url: req.originalUrl,
198
+ path: req.path
199
+ };
200
+ }
240
201
  constructor(){
241
202
  _define_property(this, "logger", _winstonloggerclass.instance);
242
203
  }
@@ -8,8 +8,8 @@ Object.defineProperty(exports, "CacheModule", {
8
8
  return CacheModule;
9
9
  }
10
10
  });
11
- const _classes = require("@flusys/nestjs-shared/classes");
12
- const _constants = require("@flusys/nestjs-shared/constants");
11
+ const _hybridcacheclass = require("../../classes/hybrid-cache.class");
12
+ const _constants = require("../../constants");
13
13
  const _common = require("@nestjs/common");
14
14
  function _ts_decorate(decorators, target, key, desc) {
15
15
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -25,7 +25,7 @@ let CacheModule = class CacheModule {
25
25
  providers: [
26
26
  {
27
27
  provide: _constants.CACHE_INSTANCE,
28
- useFactory: ()=>new _classes.HybridCache(memoryTtl, memorySize)
28
+ useFactory: ()=>new _hybridcacheclass.HybridCache(memoryTtl, memorySize)
29
29
  }
30
30
  ],
31
31
  exports: [
@@ -87,24 +87,12 @@ let DataSourceModule = class DataSourceModule {
87
87
  provide: options.useClass,
88
88
  useClass: options.useClass
89
89
  },
90
- {
91
- provide: _nestjscore.MODULE_OPTIONS,
92
- useFactory: async (factory)=>factory.createOptions(),
93
- inject: [
94
- options.useClass
95
- ]
96
- }
90
+ this.createFactoryProvider(options.useClass)
97
91
  ];
98
92
  }
99
93
  if (options.useExisting) {
100
94
  return [
101
- {
102
- provide: _nestjscore.MODULE_OPTIONS,
103
- useFactory: async (factory)=>factory.createOptions(),
104
- inject: [
105
- options.useExisting
106
- ]
107
- }
95
+ this.createFactoryProvider(options.useExisting)
108
96
  ];
109
97
  }
110
98
  return [
@@ -114,6 +102,15 @@ let DataSourceModule = class DataSourceModule {
114
102
  }
115
103
  ];
116
104
  }
105
+ static createFactoryProvider(factoryClass) {
106
+ return {
107
+ provide: _nestjscore.MODULE_OPTIONS,
108
+ useFactory: async (factory)=>factory.createOptions(),
109
+ inject: [
110
+ factoryClass
111
+ ]
112
+ };
113
+ }
117
114
  };
118
115
  DataSourceModule = _ts_decorate([
119
116
  (0, _common.Module)({})
@@ -42,7 +42,6 @@ function _ts_param(paramIndex, decorator) {
42
42
  };
43
43
  }
44
44
  let MultiTenantDataSourceService = class MultiTenantDataSourceService {
45
- // Initialization
46
45
  initializeFromOptions() {
47
46
  if (!this.options) return;
48
47
  if (!MultiTenantDataSourceService.initialized) {
@@ -55,100 +54,57 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
55
54
  MultiTenantDataSourceService.tenantsRegistry.set(tenant.id, tenant);
56
55
  });
57
56
  }
58
- // Public API
59
- /**
60
- * Set custom tenant header name
61
- */ setTenantHeader(header) {
57
+ setTenantHeader(header) {
62
58
  this.tenantHeader = header;
63
59
  }
64
- /**
65
- * Get current database mode
66
- */ getDatabaseMode() {
60
+ getDatabaseMode() {
67
61
  return this.options?.bootstrapAppConfig?.databaseMode ?? 'single';
68
62
  }
69
- /**
70
- * Check if running in multi-tenant mode
71
- */ isMultiTenant() {
63
+ isMultiTenant() {
72
64
  return this.getDatabaseMode() === 'multi-tenant';
73
65
  }
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() {
66
+ getCurrentTenantId() {
79
67
  if (!this.request) return null;
80
68
  const tenantId = this.request.headers[this.tenantHeader];
81
69
  if (!tenantId) return null;
82
- // Validate tenant ID format to prevent injection attacks
83
70
  if (!/^[a-zA-Z0-9_-]+$/.test(tenantId)) {
84
- throw new _common.BadRequestException('Invalid tenant ID format. Only alphanumeric characters, hyphens, and underscores are allowed.');
71
+ throw new _common.BadRequestException('Invalid tenant ID format');
85
72
  }
86
73
  return tenantId;
87
74
  }
88
- /**
89
- * Get current tenant config from request header
90
- */ getCurrentTenant() {
75
+ getCurrentTenant() {
91
76
  const tenantId = this.getCurrentTenantId();
92
77
  return tenantId ? this.getTenant(tenantId) : null;
93
78
  }
94
- /**
95
- * Get tenant config by ID
96
- */ getTenant(tenantId) {
79
+ getTenant(tenantId) {
97
80
  return MultiTenantDataSourceService.tenantsRegistry.get(tenantId) ?? null;
98
81
  }
99
- /**
100
- * Get all registered tenants
101
- */ getAllTenants() {
82
+ getAllTenants() {
102
83
  return Array.from(MultiTenantDataSourceService.tenantsRegistry.values());
103
84
  }
104
- /**
105
- * Get only active tenants
106
- */ getActiveTenants() {
85
+ getActiveTenants() {
107
86
  return this.getAllTenants();
108
87
  }
109
- // DataSource Access
110
- /**
111
- * Get DataSource for current context (tenant or single)
112
- */ async getDataSource() {
88
+ async getDataSource() {
113
89
  return this.isMultiTenant() ? this.getTenantDataSource() : this.getSingleDataSource();
114
90
  }
115
- /**
116
- * Get DataSource for specific tenant
117
- */ async getDataSourceForTenant(tenantId) {
91
+ async getDataSourceForTenant(tenantId) {
118
92
  const tenant = this.getTenant(tenantId);
119
- if (!tenant) {
120
- throw new Error(`Tenant '${tenantId}' not found`);
121
- }
93
+ if (!tenant) throw new Error(`Tenant '${tenantId}' not found`);
122
94
  return this.getOrCreateTenantConnection(tenant);
123
95
  }
124
- /**
125
- * Set external DataSource (for single-tenant mode)
126
- */ setDataSource(dataSource) {
96
+ setDataSource(dataSource) {
127
97
  MultiTenantDataSourceService.singleDataSource = dataSource;
128
98
  }
129
- // Repository Access
130
- /**
131
- * Get repository for entity in current context
132
- */ async getRepository(entity) {
99
+ async getRepository(entity) {
133
100
  const dataSource = await this.getDataSource();
134
101
  return dataSource.getRepository(entity);
135
102
  }
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) {
103
+ async withTenant(tenantId, callback) {
146
104
  const dataSource = await this.getDataSourceForTenant(tenantId);
147
105
  return callback(dataSource);
148
106
  }
149
- /**
150
- * Execute callback for all active tenants
151
- */ async forAllTenants(callback) {
107
+ async forAllTenants(callback) {
152
108
  const results = new Map();
153
109
  for (const tenant of this.getActiveTenants()){
154
110
  try {
@@ -160,22 +116,14 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
160
116
  }
161
117
  return results;
162
118
  }
163
- // Tenant Management
164
- /**
165
- * Register a new tenant at runtime
166
- */ registerTenant(tenant) {
119
+ registerTenant(tenant) {
167
120
  MultiTenantDataSourceService.tenantsRegistry.set(tenant.id, tenant);
168
121
  }
169
- /**
170
- * Remove tenant and close its connection
171
- */ async removeTenant(tenantId) {
122
+ async removeTenant(tenantId) {
172
123
  await this.closeTenantConnection(tenantId);
173
124
  MultiTenantDataSourceService.tenantsRegistry.delete(tenantId);
174
125
  }
175
- // Connection Lifecycle
176
- /**
177
- * Close specific tenant connection
178
- */ async closeTenantConnection(tenantId) {
126
+ async closeTenantConnection(tenantId) {
179
127
  const connection = MultiTenantDataSourceService.tenantConnections.get(tenantId);
180
128
  if (connection?.isInitialized) {
181
129
  await connection.destroy();
@@ -183,16 +131,12 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
183
131
  this.logger.log(`Closed connection for tenant: ${tenantId}`);
184
132
  }
185
133
  }
186
- /**
187
- * Lifecycle hook - cleanup on module destroy
188
- */ async onModuleDestroy() {
134
+ async onModuleDestroy() {
189
135
  for (const [tenantId] of MultiTenantDataSourceService.tenantConnections){
190
136
  await this.closeTenantConnection(tenantId);
191
137
  }
192
138
  }
193
- /**
194
- * Reset all static state (useful for testing)
195
- */ static reset() {
139
+ static reset() {
196
140
  MultiTenantDataSourceService.initialized = false;
197
141
  MultiTenantDataSourceService.singleDataSource = null;
198
142
  MultiTenantDataSourceService.singleConnectionLock = null;
@@ -236,20 +180,11 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
236
180
  }
237
181
  return this.getOrCreateTenantConnection(tenant);
238
182
  }
239
- /**
240
- * Get or create connection for tenant with locking to prevent race conditions
241
- */ async getOrCreateTenantConnection(tenant) {
242
- // Return existing initialized connection
183
+ async getOrCreateTenantConnection(tenant) {
243
184
  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
185
+ if (existing?.isInitialized) return existing;
248
186
  const pendingConnection = MultiTenantDataSourceService.connectionLocks.get(tenant.id);
249
- if (pendingConnection) {
250
- return pendingConnection;
251
- }
252
- // Create connection with lock to prevent race conditions
187
+ if (pendingConnection) return pendingConnection;
253
188
  const config = this.buildTenantDatabaseConfig(tenant);
254
189
  const connectionPromise = this.createDataSourceFromConfig(config);
255
190
  MultiTenantDataSourceService.connectionLocks.set(tenant.id, connectionPromise);
@@ -262,18 +197,12 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
262
197
  MultiTenantDataSourceService.connectionLocks.delete(tenant.id);
263
198
  }
264
199
  }
265
- /**
266
- * Get default database config (supports both naming conventions)
267
- */ getDefaultDatabaseConfig() {
200
+ getDefaultDatabaseConfig() {
268
201
  return this.options?.defaultDatabaseConfig ?? this.options?.tenantDefaultDatabaseConfig;
269
202
  }
270
- /**
271
- * Build database config for tenant (merges with default)
272
- */ buildTenantDatabaseConfig(tenant) {
203
+ buildTenantDatabaseConfig(tenant) {
273
204
  const defaultConfig = this.getDefaultDatabaseConfig();
274
- if (!defaultConfig) {
275
- throw new Error('No default database config for multi-tenant mode.');
276
- }
205
+ if (!defaultConfig) throw new Error('No default database config for multi-tenant mode.');
277
206
  return {
278
207
  type: defaultConfig.type,
279
208
  host: tenant.host ?? defaultConfig.host,
@@ -283,15 +212,7 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
283
212
  database: tenant.database
284
213
  };
285
214
  }
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 = []) {
215
+ async createDataSourceFromConfig(config, entities = []) {
295
216
  const dataSource = new _typeorm.DataSource({
296
217
  type: config.type,
297
218
  host: config.host,
@@ -303,16 +224,13 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
303
224
  synchronize: false,
304
225
  namingStrategy: new _typeormnamingstrategies.SnakeNamingStrategy()
305
226
  });
306
- if (!dataSource.isInitialized) {
307
- await dataSource.initialize();
308
- }
227
+ if (!dataSource.isInitialized) await dataSource.initialize();
309
228
  return dataSource;
310
229
  }
311
230
  constructor(options, request){
312
231
  _define_property(this, "options", void 0);
313
232
  _define_property(this, "request", void 0);
314
233
  _define_property(this, "logger", void 0);
315
- // Instance state
316
234
  _define_property(this, "tenantHeader", void 0);
317
235
  this.options = options;
318
236
  this.request = request;
@@ -321,12 +239,10 @@ let MultiTenantDataSourceService = class MultiTenantDataSourceService {
321
239
  this.initializeFromOptions();
322
240
  }
323
241
  };
324
- // Static state shared across all instances
325
242
  _define_property(MultiTenantDataSourceService, "tenantConnections", new Map());
326
243
  _define_property(MultiTenantDataSourceService, "singleDataSource", null);
327
244
  _define_property(MultiTenantDataSourceService, "tenantsRegistry", new Map());
328
245
  _define_property(MultiTenantDataSourceService, "initialized", false);
329
- // Connection locks to prevent race conditions during concurrent connection creation
330
246
  _define_property(MultiTenantDataSourceService, "connectionLocks", new Map());
331
247
  _define_property(MultiTenantDataSourceService, "singleConnectionLock", null);
332
248
  MultiTenantDataSourceService = _ts_decorate([