nest-multitenant 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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +343 -0
  3. package/dist/constants.d.ts +7 -0
  4. package/dist/constants.js +11 -0
  5. package/dist/constants.js.map +1 -0
  6. package/dist/decorators/tenant.decorator.d.ts +4 -0
  7. package/dist/decorators/tenant.decorator.js +29 -0
  8. package/dist/decorators/tenant.decorator.js.map +1 -0
  9. package/dist/exceptions/tenant.exceptions.d.ts +10 -0
  10. package/dist/exceptions/tenant.exceptions.js +35 -0
  11. package/dist/exceptions/tenant.exceptions.js.map +1 -0
  12. package/dist/guards/tenant.guard.d.ts +4 -0
  13. package/dist/guards/tenant.guard.js +25 -0
  14. package/dist/guards/tenant.guard.js.map +1 -0
  15. package/dist/index.d.ts +12 -0
  16. package/dist/index.js +29 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/interfaces/multitenant-options.interface.d.ts +30 -0
  19. package/dist/interfaces/multitenant-options.interface.js +17 -0
  20. package/dist/interfaces/multitenant-options.interface.js.map +1 -0
  21. package/dist/interfaces/tenant-resolver.interface.d.ts +11 -0
  22. package/dist/interfaces/tenant-resolver.interface.js +3 -0
  23. package/dist/interfaces/tenant-resolver.interface.js.map +1 -0
  24. package/dist/interfaces/tenant.interface.d.ts +16 -0
  25. package/dist/interfaces/tenant.interface.js +3 -0
  26. package/dist/interfaces/tenant.interface.js.map +1 -0
  27. package/dist/middleware/tenant.middleware.d.ts +22 -0
  28. package/dist/middleware/tenant.middleware.js +100 -0
  29. package/dist/middleware/tenant.middleware.js.map +1 -0
  30. package/dist/multitenant.module.d.ts +11 -0
  31. package/dist/multitenant.module.js +115 -0
  32. package/dist/multitenant.module.js.map +1 -0
  33. package/dist/resolvers/header.resolver.d.ts +11 -0
  34. package/dist/resolvers/header.resolver.js +39 -0
  35. package/dist/resolvers/header.resolver.js.map +1 -0
  36. package/dist/resolvers/path.resolver.d.ts +10 -0
  37. package/dist/resolvers/path.resolver.js +43 -0
  38. package/dist/resolvers/path.resolver.js.map +1 -0
  39. package/dist/resolvers/subdomain.resolver.d.ts +8 -0
  40. package/dist/resolvers/subdomain.resolver.js +44 -0
  41. package/dist/resolvers/subdomain.resolver.js.map +1 -0
  42. package/package.json +92 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alireza Aminzadeh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,343 @@
1
+ # nest-multitenant
2
+
3
+ [![npm version](https://badge.fury.io/js/nest-multitenant.svg)](https://www.npmjs.com/package/nest-multitenant)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Test Coverage](https://img.shields.io/badge/coverage-80%25-brightgreen.svg)](https://github.com/syeedalireza/nest-multitenant)
6
+
7
+ Enterprise-grade multi-tenancy middleware for NestJS applications with support for multiple tenant resolution strategies and database isolation patterns.
8
+
9
+ ## 🚀 Features
10
+
11
+ - **Multiple Tenant Resolution Strategies**
12
+ - Subdomain-based (`tenant1.example.com`)
13
+ - Header-based (`X-Tenant-ID`)
14
+ - Path-based (`/tenant1/api/users`)
15
+ - Custom resolver support
16
+
17
+ - **Database Isolation Patterns**
18
+ - Shared database with tenant_id column
19
+ - Schema per tenant
20
+ - Database per tenant
21
+
22
+ - **Production-Ready**
23
+ - TypeScript with strict typing
24
+ - Comprehensive test coverage (>80%)
25
+ - Request-scoped tenant context
26
+ - Tenant validation and guards
27
+ - Graceful error handling
28
+
29
+ - **Developer-Friendly**
30
+ - Easy integration with TypeORM and Prisma
31
+ - Decorators for accessing tenant context
32
+ - Configurable ignored paths
33
+ - Caching support
34
+
35
+ ## 📦 Installation
36
+
37
+ ```bash
38
+ npm install nest-multitenant
39
+ ```
40
+
41
+ **Peer Dependencies:**
42
+
43
+ ```bash
44
+ npm install @nestjs/common @nestjs/core reflect-metadata rxjs
45
+ ```
46
+
47
+ ## 🔧 Quick Start
48
+
49
+ ### 1. Implement TenantStore
50
+
51
+ Create a service that implements the `TenantStore` interface:
52
+
53
+ ```typescript
54
+ import { Injectable } from '@nestjs/common';
55
+ import { TenantStore, Tenant } from 'nest-multitenant';
56
+
57
+ @Injectable()
58
+ export class DatabaseTenantStore implements TenantStore {
59
+ async findById(id: string): Promise<Tenant | null> {
60
+ // Fetch tenant from your database
61
+ return {
62
+ id: 'tenant1',
63
+ name: 'Tenant One',
64
+ subdomain: 'tenant1',
65
+ isActive: true,
66
+ };
67
+ }
68
+
69
+ async findBySubdomain(subdomain: string): Promise<Tenant | null> {
70
+ // Fetch tenant by subdomain
71
+ return null;
72
+ }
73
+
74
+ async findByCustomKey(key: string, value: string): Promise<Tenant | null> {
75
+ return null;
76
+ }
77
+
78
+ async findAll(): Promise<Tenant[]> {
79
+ return [];
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### 2. Register the Module
85
+
86
+ ```typescript
87
+ import { Module } from '@nestjs/common';
88
+ import { MultitenantModule, TenantResolutionStrategy } from 'nest-multitenant';
89
+ import { DatabaseTenantStore } from './tenant.store';
90
+
91
+ @Module({
92
+ imports: [
93
+ MultitenantModule.forRoot({
94
+ tenantResolutionStrategy: TenantResolutionStrategy.HEADER,
95
+ tenantStore: DatabaseTenantStore,
96
+ headerName: 'X-Tenant-ID',
97
+ throwOnMissingTenant: true,
98
+ ignoredPaths: ['/health', '/metrics'],
99
+ }),
100
+ ],
101
+ })
102
+ export class AppModule {}
103
+ ```
104
+
105
+ ### 3. Use Tenant Context in Controllers
106
+
107
+ ```typescript
108
+ import { Controller, Get } from '@nestjs/common';
109
+ import { CurrentTenant, Tenant } from 'nest-multitenant';
110
+
111
+ @Controller('users')
112
+ export class UsersController {
113
+ @Get()
114
+ async getUsers(@CurrentTenant() tenant: Tenant) {
115
+ return `Fetching users for tenant: ${tenant.name}`;
116
+ }
117
+ }
118
+ ```
119
+
120
+ ## 📖 Configuration Options
121
+
122
+ ### Module Options
123
+
124
+ | Option | Type | Default | Description |
125
+ |--------|------|---------|-------------|
126
+ | `tenantResolutionStrategy` | `TenantResolutionStrategy` | **Required** | How to resolve the tenant from requests |
127
+ | `tenantStore` | `Type<TenantStore>` | **Required** | Implementation of tenant data store |
128
+ | `headerName` | `string` | `'X-Tenant-ID'` | Header name for header-based resolution |
129
+ | `pathPrefix` | `string` | - | Path prefix for path-based resolution |
130
+ | `customResolver` | `Type<TenantResolver>` | - | Custom resolver (required for CUSTOM strategy) |
131
+ | `throwOnMissingTenant` | `boolean` | `true` | Throw exception if tenant cannot be resolved |
132
+ | `defaultTenantId` | `string` | - | Default tenant when resolution fails |
133
+ | `enableCaching` | `boolean` | `false` | Enable tenant data caching |
134
+ | `cacheTTL` | `number` | `3600` | Cache TTL in seconds |
135
+ | `ignoredPaths` | `string[]` | `[]` | Paths to skip tenant resolution |
136
+
137
+ ## 🎯 Resolution Strategies
138
+
139
+ ### Subdomain Strategy
140
+
141
+ Resolves tenants from subdomain:
142
+
143
+ ```typescript
144
+ MultitenantModule.forRoot({
145
+ tenantResolutionStrategy: TenantResolutionStrategy.SUBDOMAIN,
146
+ tenantStore: DatabaseTenantStore,
147
+ });
148
+
149
+ // tenant1.example.com -> resolves to tenant with subdomain='tenant1'
150
+ ```
151
+
152
+ ### Header Strategy
153
+
154
+ Resolves tenants from HTTP header:
155
+
156
+ ```typescript
157
+ MultitenantModule.forRoot({
158
+ tenantResolutionStrategy: TenantResolutionStrategy.HEADER,
159
+ headerName: 'X-Tenant-ID',
160
+ tenantStore: DatabaseTenantStore,
161
+ });
162
+
163
+ // Request with header: X-Tenant-ID: tenant1
164
+ ```
165
+
166
+ ### Path Strategy
167
+
168
+ Resolves tenants from URL path:
169
+
170
+ ```typescript
171
+ MultitenantModule.forRoot({
172
+ tenantResolutionStrategy: TenantResolutionStrategy.PATH,
173
+ tenantStore: DatabaseTenantStore,
174
+ });
175
+
176
+ // /tenant1/api/users -> resolves to tenant with id='tenant1'
177
+ ```
178
+
179
+ ### Custom Strategy
180
+
181
+ Implement your own resolution logic:
182
+
183
+ ```typescript
184
+ @Injectable()
185
+ export class CustomTenantResolver implements TenantResolver {
186
+ constructor(@Inject(TENANT_STORE) private tenantStore: TenantStore) {}
187
+
188
+ async resolve(request: Request): Promise<Tenant | null> {
189
+ const apiKey = request.headers['x-api-key'];
190
+ return this.tenantStore.findByCustomKey('apiKey', apiKey);
191
+ }
192
+ }
193
+
194
+ MultitenantModule.forRoot({
195
+ tenantResolutionStrategy: TenantResolutionStrategy.CUSTOM,
196
+ customResolver: CustomTenantResolver,
197
+ tenantStore: DatabaseTenantStore,
198
+ });
199
+ ```
200
+
201
+ ## 🛡️ Guards and Decorators
202
+
203
+ ### TenantGuard
204
+
205
+ Ensure a tenant has been resolved:
206
+
207
+ ```typescript
208
+ import { Controller, Get, UseGuards } from '@nestjs/common';
209
+ import { TenantGuard } from 'nest-multitenant';
210
+
211
+ @Controller('protected')
212
+ @UseGuards(TenantGuard)
213
+ export class ProtectedController {
214
+ // All routes require a valid tenant
215
+ }
216
+ ```
217
+
218
+ ### Decorators
219
+
220
+ ```typescript
221
+ import { CurrentTenant, TenantCtx, TenantProperty } from 'nest-multitenant';
222
+
223
+ @Get()
224
+ async example1(@CurrentTenant() tenant: Tenant) {
225
+ // Access full tenant object
226
+ }
227
+
228
+ @Get()
229
+ async example2(@TenantCtx() context: TenantContext) {
230
+ // Access tenant context with metadata
231
+ console.log(context.source); // 'header', 'subdomain', etc.
232
+ }
233
+
234
+ @Get()
235
+ async example3(@TenantProperty('id') tenantId: string) {
236
+ // Access specific tenant property
237
+ }
238
+ ```
239
+
240
+ ## 🗄️ Database Integration
241
+
242
+ ### TypeORM Example
243
+
244
+ ```typescript
245
+ import { Injectable, Scope } from '@nestjs/common';
246
+ import { InjectRepository } from '@nestjs/typeorm';
247
+ import { Repository } from 'typeorm';
248
+ import { REQUEST } from '@nestjs/core';
249
+ import { Request } from 'express';
250
+
251
+ @Injectable({ scope: Scope.REQUEST })
252
+ export class UserService {
253
+ private tenantId: string;
254
+
255
+ constructor(
256
+ @Inject(REQUEST) request: Request,
257
+ @InjectRepository(User) private userRepo: Repository<User>,
258
+ ) {
259
+ this.tenantId = request.tenantContext?.tenant.id || '';
260
+ }
261
+
262
+ async findAll() {
263
+ return this.userRepo.find({ where: { tenantId: this.tenantId } });
264
+ }
265
+ }
266
+ ```
267
+
268
+ ### Prisma Example
269
+
270
+ ```typescript
271
+ import { Injectable } from '@nestjs/common';
272
+ import { PrismaService } from './prisma.service';
273
+
274
+ @Injectable()
275
+ export class UserService {
276
+ constructor(private prisma: PrismaService) {}
277
+
278
+ async findAll(tenantId: string) {
279
+ return this.prisma.user.findMany({
280
+ where: { tenantId },
281
+ });
282
+ }
283
+ }
284
+ ```
285
+
286
+ ## 🔒 Security Considerations
287
+
288
+ - Always validate tenant IDs to prevent tenant isolation bypass
289
+ - Use HTTPS in production for header-based resolution
290
+ - Implement rate limiting per tenant
291
+ - Audit tenant switching actions
292
+ - Consider row-level security in your database
293
+
294
+ ## 🧪 Testing
295
+
296
+ Run tests:
297
+
298
+ ```bash
299
+ npm test
300
+ ```
301
+
302
+ With coverage:
303
+
304
+ ```bash
305
+ npm run test:cov
306
+ ```
307
+
308
+ ## 📝 Example Application
309
+
310
+ See the [examples](./examples) directory for complete working examples with:
311
+ - Docker setup
312
+ - TypeORM integration
313
+ - API examples
314
+ - Testing strategies
315
+
316
+ ## 🤝 Contributing
317
+
318
+ Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md) for details.
319
+
320
+ ## 📄 License
321
+
322
+ MIT License - see [LICENSE](./LICENSE) file for details.
323
+
324
+ ## 👤 Author
325
+
326
+ **Alireza Aminzadeh**
327
+ - Email: alireza.aminzadeh@hotmail.com
328
+ - GitHub: [@syeedalireza](https://github.com/syeedalireza)
329
+ - NPM: [@syeedalireza](https://www.npmjs.com/~syeedalireza)
330
+
331
+ ## ⭐ Support
332
+
333
+ If this package helped you, please give it a ⭐ on [GitHub](https://github.com/syeedalireza/nest-multitenant)!
334
+
335
+ ## 🔗 Related Packages
336
+
337
+ - [@nestjs/typeorm](https://www.npmjs.com/package/@nestjs/typeorm)
338
+ - [prisma](https://www.npmjs.com/package/prisma)
339
+ - [@nestjs/passport](https://www.npmjs.com/package/@nestjs/passport)
340
+
341
+ ---
342
+
343
+ **Built with ❤️ for the NestJS community**
@@ -0,0 +1,7 @@
1
+ export declare const MULTITENANT_OPTIONS = "MULTITENANT_OPTIONS";
2
+ export declare const TENANT_STORE = "TENANT_STORE";
3
+ export declare const TENANT_RESOLVER = "TENANT_RESOLVER";
4
+ export declare const TENANT_CONTEXT = "TENANT_CONTEXT";
5
+ export declare const TENANT_METADATA_KEY = "tenant:metadata";
6
+ export declare const DEFAULT_HEADER_NAME = "X-Tenant-ID";
7
+ export declare const DEFAULT_CACHE_TTL = 3600;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CACHE_TTL = exports.DEFAULT_HEADER_NAME = exports.TENANT_METADATA_KEY = exports.TENANT_CONTEXT = exports.TENANT_RESOLVER = exports.TENANT_STORE = exports.MULTITENANT_OPTIONS = void 0;
4
+ exports.MULTITENANT_OPTIONS = 'MULTITENANT_OPTIONS';
5
+ exports.TENANT_STORE = 'TENANT_STORE';
6
+ exports.TENANT_RESOLVER = 'TENANT_RESOLVER';
7
+ exports.TENANT_CONTEXT = 'TENANT_CONTEXT';
8
+ exports.TENANT_METADATA_KEY = 'tenant:metadata';
9
+ exports.DEFAULT_HEADER_NAME = 'X-Tenant-ID';
10
+ exports.DEFAULT_CACHE_TTL = 3600;
11
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAGa,QAAA,mBAAmB,GAAG,qBAAqB,CAAC;AAC5C,QAAA,YAAY,GAAG,cAAc,CAAC;AAC9B,QAAA,eAAe,GAAG,iBAAiB,CAAC;AACpC,QAAA,cAAc,GAAG,gBAAgB,CAAC;AAKlC,QAAA,mBAAmB,GAAG,iBAAiB,CAAC;AAKxC,QAAA,mBAAmB,GAAG,aAAa,CAAC;AACpC,QAAA,iBAAiB,GAAG,IAAI,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Tenant } from '../interfaces/tenant.interface';
2
+ export declare const CurrentTenant: (...dataOrPipes: unknown[]) => ParameterDecorator;
3
+ export declare const TenantCtx: (...dataOrPipes: unknown[]) => ParameterDecorator;
4
+ export declare const TenantProperty: (...dataOrPipes: (keyof Tenant | import("@nestjs/common").PipeTransform<any, any> | import("@nestjs/common").Type<import("@nestjs/common").PipeTransform<any, any>>)[]) => ParameterDecorator;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TenantProperty = exports.TenantCtx = exports.CurrentTenant = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.CurrentTenant = (0, common_1.createParamDecorator)((data, ctx) => {
6
+ const request = ctx.switchToHttp().getRequest();
7
+ const tenantContext = request.tenantContext;
8
+ if (!tenantContext) {
9
+ throw new Error('Tenant context not found. Ensure TenantMiddleware is properly configured.');
10
+ }
11
+ return tenantContext.tenant;
12
+ });
13
+ exports.TenantCtx = (0, common_1.createParamDecorator)((data, ctx) => {
14
+ const request = ctx.switchToHttp().getRequest();
15
+ const tenantContext = request.tenantContext;
16
+ if (!tenantContext) {
17
+ throw new Error('Tenant context not found. Ensure TenantMiddleware is properly configured.');
18
+ }
19
+ return tenantContext;
20
+ });
21
+ exports.TenantProperty = (0, common_1.createParamDecorator)((property, ctx) => {
22
+ const request = ctx.switchToHttp().getRequest();
23
+ const tenantContext = request.tenantContext;
24
+ if (!tenantContext) {
25
+ throw new Error('Tenant context not found. Ensure TenantMiddleware is properly configured.');
26
+ }
27
+ return tenantContext.tenant[property];
28
+ });
29
+ //# sourceMappingURL=tenant.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant.decorator.js","sourceRoot":"","sources":["../../src/decorators/tenant.decorator.ts"],"names":[],"mappings":";;;AAAA,2CAAwE;AAe3D,QAAA,aAAa,GAAG,IAAA,6BAAoB,EAC/C,CAAC,IAAa,EAAE,GAAqB,EAAU,EAAE;IAC/C,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;IAChD,MAAM,aAAa,GAA8B,OAAO,CAAC,aAAa,CAAC;IAEvE,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;IAC/F,CAAC;IAED,OAAO,aAAa,CAAC,MAAM,CAAC;AAC9B,CAAC,CACF,CAAC;AAeW,QAAA,SAAS,GAAG,IAAA,6BAAoB,EAC3C,CAAC,IAAa,EAAE,GAAqB,EAAiB,EAAE;IACtD,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;IAChD,MAAM,aAAa,GAA8B,OAAO,CAAC,aAAa,CAAC;IAEvE,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;IAC/F,CAAC;IAED,OAAO,aAAa,CAAC;AACvB,CAAC,CACF,CAAC;AAeW,QAAA,cAAc,GAAG,IAAA,6BAAoB,EAChD,CAAC,QAAsB,EAAE,GAAqB,EAAO,EAAE;IACrD,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;IAChD,MAAM,aAAa,GAA8B,OAAO,CAAC,aAAa,CAAC;IAEvE,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;IAC/F,CAAC;IAED,OAAO,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AACxC,CAAC,CACF,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { HttpException } from '@nestjs/common';
2
+ export declare class TenantNotResolvedException extends HttpException {
3
+ constructor(message?: string);
4
+ }
5
+ export declare class TenantNotFoundException extends HttpException {
6
+ constructor(identifier: string);
7
+ }
8
+ export declare class TenantInactiveException extends HttpException {
9
+ constructor(tenantId: string);
10
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TenantInactiveException = exports.TenantNotFoundException = exports.TenantNotResolvedException = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ class TenantNotResolvedException extends common_1.HttpException {
6
+ constructor(message = 'Tenant could not be resolved from the request') {
7
+ super({
8
+ statusCode: common_1.HttpStatus.BAD_REQUEST,
9
+ message,
10
+ error: 'Tenant Not Resolved',
11
+ }, common_1.HttpStatus.BAD_REQUEST);
12
+ }
13
+ }
14
+ exports.TenantNotResolvedException = TenantNotResolvedException;
15
+ class TenantNotFoundException extends common_1.HttpException {
16
+ constructor(identifier) {
17
+ super({
18
+ statusCode: common_1.HttpStatus.NOT_FOUND,
19
+ message: `Tenant with identifier '${identifier}' was not found`,
20
+ error: 'Tenant Not Found',
21
+ }, common_1.HttpStatus.NOT_FOUND);
22
+ }
23
+ }
24
+ exports.TenantNotFoundException = TenantNotFoundException;
25
+ class TenantInactiveException extends common_1.HttpException {
26
+ constructor(tenantId) {
27
+ super({
28
+ statusCode: common_1.HttpStatus.FORBIDDEN,
29
+ message: `Tenant '${tenantId}' is not active`,
30
+ error: 'Tenant Inactive',
31
+ }, common_1.HttpStatus.FORBIDDEN);
32
+ }
33
+ }
34
+ exports.TenantInactiveException = TenantInactiveException;
35
+ //# sourceMappingURL=tenant.exceptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant.exceptions.js","sourceRoot":"","sources":["../../src/exceptions/tenant.exceptions.ts"],"names":[],"mappings":";;;AAAA,2CAA2D;AAK3D,MAAa,0BAA2B,SAAQ,sBAAa;IAC3D,YAAY,OAAO,GAAG,+CAA+C;QACnE,KAAK,CACH;YACE,UAAU,EAAE,mBAAU,CAAC,WAAW;YAClC,OAAO;YACP,KAAK,EAAE,qBAAqB;SAC7B,EACD,mBAAU,CAAC,WAAW,CACvB,CAAC;IACJ,CAAC;CACF;AAXD,gEAWC;AAKD,MAAa,uBAAwB,SAAQ,sBAAa;IACxD,YAAY,UAAkB;QAC5B,KAAK,CACH;YACE,UAAU,EAAE,mBAAU,CAAC,SAAS;YAChC,OAAO,EAAE,2BAA2B,UAAU,iBAAiB;YAC/D,KAAK,EAAE,kBAAkB;SAC1B,EACD,mBAAU,CAAC,SAAS,CACrB,CAAC;IACJ,CAAC;CACF;AAXD,0DAWC;AAKD,MAAa,uBAAwB,SAAQ,sBAAa;IACxD,YAAY,QAAgB;QAC1B,KAAK,CACH;YACE,UAAU,EAAE,mBAAU,CAAC,SAAS;YAChC,OAAO,EAAE,WAAW,QAAQ,iBAAiB;YAC7C,KAAK,EAAE,iBAAiB;SACzB,EACD,mBAAU,CAAC,SAAS,CACrB,CAAC;IACJ,CAAC;CACF;AAXD,0DAWC"}
@@ -0,0 +1,4 @@
1
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ export declare class TenantGuard implements CanActivate {
3
+ canActivate(context: ExecutionContext): boolean;
4
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.TenantGuard = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const tenant_exceptions_1 = require("../exceptions/tenant.exceptions");
12
+ let TenantGuard = class TenantGuard {
13
+ canActivate(context) {
14
+ const request = context.switchToHttp().getRequest();
15
+ if (!request.tenantContext || !request.tenantContext.tenant) {
16
+ throw new tenant_exceptions_1.TenantNotResolvedException();
17
+ }
18
+ return true;
19
+ }
20
+ };
21
+ exports.TenantGuard = TenantGuard;
22
+ exports.TenantGuard = TenantGuard = __decorate([
23
+ (0, common_1.Injectable)()
24
+ ], TenantGuard);
25
+ //# sourceMappingURL=tenant.guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant.guard.js","sourceRoot":"","sources":["../../src/guards/tenant.guard.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA2E;AAC3E,uEAA6E;AAgBtE,IAAM,WAAW,GAAjB,MAAM,WAAW;IACtB,WAAW,CAAC,OAAyB;QACnC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAEpD,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC;YAC5D,MAAM,IAAI,8CAA0B,EAAE,CAAC;QACzC,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAVY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;GACA,WAAW,CAUvB"}
@@ -0,0 +1,12 @@
1
+ export * from './multitenant.module';
2
+ export * from './interfaces/tenant.interface';
3
+ export * from './interfaces/tenant-resolver.interface';
4
+ export * from './interfaces/multitenant-options.interface';
5
+ export * from './decorators/tenant.decorator';
6
+ export * from './guards/tenant.guard';
7
+ export * from './exceptions/tenant.exceptions';
8
+ export * from './resolvers/subdomain.resolver';
9
+ export * from './resolvers/header.resolver';
10
+ export * from './resolvers/path.resolver';
11
+ export * from './middleware/tenant.middleware';
12
+ export * from './constants';
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./multitenant.module"), exports);
18
+ __exportStar(require("./interfaces/tenant.interface"), exports);
19
+ __exportStar(require("./interfaces/tenant-resolver.interface"), exports);
20
+ __exportStar(require("./interfaces/multitenant-options.interface"), exports);
21
+ __exportStar(require("./decorators/tenant.decorator"), exports);
22
+ __exportStar(require("./guards/tenant.guard"), exports);
23
+ __exportStar(require("./exceptions/tenant.exceptions"), exports);
24
+ __exportStar(require("./resolvers/subdomain.resolver"), exports);
25
+ __exportStar(require("./resolvers/header.resolver"), exports);
26
+ __exportStar(require("./resolvers/path.resolver"), exports);
27
+ __exportStar(require("./middleware/tenant.middleware"), exports);
28
+ __exportStar(require("./constants"), exports);
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AACA,uDAAqC;AAGrC,gEAA8C;AAC9C,yEAAuD;AACvD,6EAA2D;AAG3D,gEAA8C;AAG9C,wDAAsC;AAGtC,iEAA+C;AAG/C,iEAA+C;AAC/C,8DAA4C;AAC5C,4DAA0C;AAG1C,iEAA+C;AAG/C,8CAA4B"}
@@ -0,0 +1,30 @@
1
+ import { ModuleMetadata, Type } from '@nestjs/common';
2
+ import { TenantResolver, TenantStore } from './tenant-resolver.interface';
3
+ export declare enum TenantResolutionStrategy {
4
+ SUBDOMAIN = "subdomain",
5
+ HEADER = "header",
6
+ PATH = "path",
7
+ CUSTOM = "custom"
8
+ }
9
+ export declare enum DatabaseIsolationStrategy {
10
+ SHARED = "shared",
11
+ SCHEMA = "schema",
12
+ DATABASE = "database"
13
+ }
14
+ export interface MultitenantModuleOptions {
15
+ tenantResolutionStrategy: TenantResolutionStrategy;
16
+ databaseIsolationStrategy?: DatabaseIsolationStrategy;
17
+ headerName?: string;
18
+ pathPrefix?: string;
19
+ customResolver?: Type<TenantResolver>;
20
+ tenantStore: Type<TenantStore>;
21
+ throwOnMissingTenant?: boolean;
22
+ defaultTenantId?: string;
23
+ enableCaching?: boolean;
24
+ cacheTTL?: number;
25
+ ignoredPaths?: string[];
26
+ }
27
+ export interface MultitenantModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
28
+ useFactory?: (...args: any[]) => Promise<MultitenantModuleOptions> | MultitenantModuleOptions;
29
+ inject?: any[];
30
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DatabaseIsolationStrategy = exports.TenantResolutionStrategy = void 0;
4
+ var TenantResolutionStrategy;
5
+ (function (TenantResolutionStrategy) {
6
+ TenantResolutionStrategy["SUBDOMAIN"] = "subdomain";
7
+ TenantResolutionStrategy["HEADER"] = "header";
8
+ TenantResolutionStrategy["PATH"] = "path";
9
+ TenantResolutionStrategy["CUSTOM"] = "custom";
10
+ })(TenantResolutionStrategy || (exports.TenantResolutionStrategy = TenantResolutionStrategy = {}));
11
+ var DatabaseIsolationStrategy;
12
+ (function (DatabaseIsolationStrategy) {
13
+ DatabaseIsolationStrategy["SHARED"] = "shared";
14
+ DatabaseIsolationStrategy["SCHEMA"] = "schema";
15
+ DatabaseIsolationStrategy["DATABASE"] = "database";
16
+ })(DatabaseIsolationStrategy || (exports.DatabaseIsolationStrategy = DatabaseIsolationStrategy = {}));
17
+ //# sourceMappingURL=multitenant-options.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multitenant-options.interface.js","sourceRoot":"","sources":["../../src/interfaces/multitenant-options.interface.ts"],"names":[],"mappings":";;;AAMA,IAAY,wBAoBX;AApBD,WAAY,wBAAwB;IAIlC,mDAAuB,CAAA;IAKvB,6CAAiB,CAAA;IAKjB,yCAAa,CAAA;IAKb,6CAAiB,CAAA;AACnB,CAAC,EApBW,wBAAwB,wCAAxB,wBAAwB,QAoBnC;AAKD,IAAY,yBAeX;AAfD,WAAY,yBAAyB;IAInC,8CAAiB,CAAA;IAKjB,8CAAiB,CAAA;IAKjB,kDAAqB,CAAA;AACvB,CAAC,EAfW,yBAAyB,yCAAzB,yBAAyB,QAepC"}
@@ -0,0 +1,11 @@
1
+ import { Request } from 'express';
2
+ import { Tenant } from './tenant.interface';
3
+ export interface TenantResolver {
4
+ resolve(request: Request): Promise<Tenant | null>;
5
+ }
6
+ export interface TenantStore {
7
+ findById(id: string): Promise<Tenant | null>;
8
+ findBySubdomain(subdomain: string): Promise<Tenant | null>;
9
+ findByCustomKey(key: string, value: string): Promise<Tenant | null>;
10
+ findAll(): Promise<Tenant[]>;
11
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=tenant-resolver.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-resolver.interface.js","sourceRoot":"","sources":["../../src/interfaces/tenant-resolver.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ export interface Tenant {
2
+ id: string;
3
+ name: string;
4
+ subdomain?: string;
5
+ database?: string;
6
+ schema?: string;
7
+ isActive: boolean;
8
+ metadata?: Record<string, any>;
9
+ createdAt?: Date;
10
+ updatedAt?: Date;
11
+ }
12
+ export interface TenantContext {
13
+ tenant: Tenant;
14
+ source: 'subdomain' | 'header' | 'path' | 'custom';
15
+ rawValue: string;
16
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=tenant.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant.interface.js","sourceRoot":"","sources":["../../src/interfaces/tenant.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,22 @@
1
+ import { NestMiddleware } from '@nestjs/common';
2
+ import { Request, Response, NextFunction } from 'express';
3
+ import { TenantResolver } from '../interfaces/tenant-resolver.interface';
4
+ import { TenantContext } from '../interfaces/tenant.interface';
5
+ import { MultitenantModuleOptions } from '../interfaces/multitenant-options.interface';
6
+ declare global {
7
+ namespace Express {
8
+ interface Request {
9
+ tenantContext?: TenantContext;
10
+ }
11
+ }
12
+ }
13
+ export declare class TenantMiddleware implements NestMiddleware {
14
+ private readonly tenantResolver;
15
+ private readonly options;
16
+ constructor(tenantResolver: TenantResolver, options: MultitenantModuleOptions);
17
+ use(req: Request, res: Response, next: NextFunction): Promise<void>;
18
+ private shouldIgnorePath;
19
+ private handleMissingTenant;
20
+ private getResolutionSource;
21
+ private getRawValue;
22
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.TenantMiddleware = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const tenant_exceptions_1 = require("../exceptions/tenant.exceptions");
18
+ const constants_1 = require("../constants");
19
+ let TenantMiddleware = class TenantMiddleware {
20
+ constructor(tenantResolver, options) {
21
+ this.tenantResolver = tenantResolver;
22
+ this.options = options;
23
+ }
24
+ async use(req, res, next) {
25
+ if (this.shouldIgnorePath(req.path)) {
26
+ return next();
27
+ }
28
+ try {
29
+ const tenant = await this.tenantResolver.resolve(req);
30
+ if (!tenant) {
31
+ return this.handleMissingTenant(req, next);
32
+ }
33
+ if (!tenant.isActive) {
34
+ throw new tenant_exceptions_1.TenantInactiveException(tenant.id);
35
+ }
36
+ req.tenantContext = {
37
+ tenant,
38
+ source: this.getResolutionSource(),
39
+ rawValue: this.getRawValue(req),
40
+ };
41
+ next();
42
+ }
43
+ catch (error) {
44
+ next(error);
45
+ }
46
+ }
47
+ shouldIgnorePath(path) {
48
+ if (!this.options.ignoredPaths || this.options.ignoredPaths.length === 0) {
49
+ return false;
50
+ }
51
+ return this.options.ignoredPaths.some((ignoredPath) => {
52
+ if (ignoredPath.endsWith('*')) {
53
+ const prefix = ignoredPath.slice(0, -1);
54
+ return path.startsWith(prefix);
55
+ }
56
+ return path === ignoredPath;
57
+ });
58
+ }
59
+ handleMissingTenant(req, next) {
60
+ const shouldThrow = this.options.throwOnMissingTenant !== false;
61
+ if (shouldThrow) {
62
+ throw new tenant_exceptions_1.TenantNotResolvedException();
63
+ }
64
+ if (this.options.defaultTenantId) {
65
+ req.tenantContext = {
66
+ tenant: {
67
+ id: this.options.defaultTenantId,
68
+ name: 'Default Tenant',
69
+ isActive: true,
70
+ },
71
+ source: 'custom',
72
+ rawValue: 'default',
73
+ };
74
+ }
75
+ next();
76
+ }
77
+ getResolutionSource() {
78
+ return this.options.tenantResolutionStrategy;
79
+ }
80
+ getRawValue(req) {
81
+ switch (this.options.tenantResolutionStrategy) {
82
+ case 'subdomain':
83
+ return req.hostname?.split('.')[0] || '';
84
+ case 'header':
85
+ return req.headers[this.options.headerName?.toLowerCase() || 'x-tenant-id'] || '';
86
+ case 'path':
87
+ return req.path.split('/')[1] || '';
88
+ default:
89
+ return '';
90
+ }
91
+ }
92
+ };
93
+ exports.TenantMiddleware = TenantMiddleware;
94
+ exports.TenantMiddleware = TenantMiddleware = __decorate([
95
+ (0, common_1.Injectable)(),
96
+ __param(0, (0, common_1.Inject)(constants_1.TENANT_RESOLVER)),
97
+ __param(1, (0, common_1.Inject)(constants_1.MULTITENANT_OPTIONS)),
98
+ __metadata("design:paramtypes", [Object, Object])
99
+ ], TenantMiddleware);
100
+ //# sourceMappingURL=tenant.middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant.middleware.js","sourceRoot":"","sources":["../../src/middleware/tenant.middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { DynamicModule, MiddlewareConsumer, NestModule } from '@nestjs/common';
2
+ import { MultitenantModuleOptions, MultitenantModuleAsyncOptions } from './interfaces/multitenant-options.interface';
3
+ export declare class MultitenantModule implements NestModule {
4
+ configure(consumer: MiddlewareConsumer): void;
5
+ static forRoot(options: MultitenantModuleOptions): DynamicModule;
6
+ static forRootAsync(options: MultitenantModuleAsyncOptions): DynamicModule;
7
+ private static createProviders;
8
+ private static createAsyncProviders;
9
+ private static createResolverProvider;
10
+ private static createResolverInstance;
11
+ }
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var MultitenantModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.MultitenantModule = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const multitenant_options_interface_1 = require("./interfaces/multitenant-options.interface");
13
+ const tenant_middleware_1 = require("./middleware/tenant.middleware");
14
+ const subdomain_resolver_1 = require("./resolvers/subdomain.resolver");
15
+ const header_resolver_1 = require("./resolvers/header.resolver");
16
+ const path_resolver_1 = require("./resolvers/path.resolver");
17
+ const tenant_guard_1 = require("./guards/tenant.guard");
18
+ const constants_1 = require("./constants");
19
+ let MultitenantModule = MultitenantModule_1 = class MultitenantModule {
20
+ configure(consumer) {
21
+ consumer.apply(tenant_middleware_1.TenantMiddleware).forRoutes('*');
22
+ }
23
+ static forRoot(options) {
24
+ const providers = this.createProviders(options);
25
+ return {
26
+ module: MultitenantModule_1,
27
+ providers,
28
+ exports: providers,
29
+ global: true,
30
+ };
31
+ }
32
+ static forRootAsync(options) {
33
+ const providers = this.createAsyncProviders(options);
34
+ return {
35
+ module: MultitenantModule_1,
36
+ imports: options.imports || [],
37
+ providers,
38
+ exports: providers,
39
+ global: true,
40
+ };
41
+ }
42
+ static createProviders(options) {
43
+ const resolverProvider = this.createResolverProvider(options);
44
+ return [
45
+ {
46
+ provide: constants_1.MULTITENANT_OPTIONS,
47
+ useValue: options,
48
+ },
49
+ {
50
+ provide: constants_1.TENANT_STORE,
51
+ useClass: options.tenantStore,
52
+ },
53
+ resolverProvider,
54
+ tenant_middleware_1.TenantMiddleware,
55
+ tenant_guard_1.TenantGuard,
56
+ ];
57
+ }
58
+ static createAsyncProviders(options) {
59
+ const optionsProvider = {
60
+ provide: constants_1.MULTITENANT_OPTIONS,
61
+ useFactory: options.useFactory,
62
+ inject: options.inject || [],
63
+ };
64
+ return [
65
+ optionsProvider,
66
+ {
67
+ provide: constants_1.TENANT_STORE,
68
+ useFactory: (opts) => {
69
+ return new opts.tenantStore();
70
+ },
71
+ inject: [constants_1.MULTITENANT_OPTIONS],
72
+ },
73
+ {
74
+ provide: constants_1.TENANT_RESOLVER,
75
+ useFactory: (opts, tenantStore) => {
76
+ return this.createResolverInstance(opts, tenantStore);
77
+ },
78
+ inject: [constants_1.MULTITENANT_OPTIONS, constants_1.TENANT_STORE],
79
+ },
80
+ tenant_middleware_1.TenantMiddleware,
81
+ tenant_guard_1.TenantGuard,
82
+ ];
83
+ }
84
+ static createResolverProvider(options) {
85
+ return {
86
+ provide: constants_1.TENANT_RESOLVER,
87
+ useFactory: (tenantStore) => {
88
+ return this.createResolverInstance(options, tenantStore);
89
+ },
90
+ inject: [constants_1.TENANT_STORE],
91
+ };
92
+ }
93
+ static createResolverInstance(options, tenantStore) {
94
+ switch (options.tenantResolutionStrategy) {
95
+ case multitenant_options_interface_1.TenantResolutionStrategy.SUBDOMAIN:
96
+ return new subdomain_resolver_1.SubdomainTenantResolver(tenantStore);
97
+ case multitenant_options_interface_1.TenantResolutionStrategy.HEADER:
98
+ return new header_resolver_1.HeaderTenantResolver(tenantStore, options);
99
+ case multitenant_options_interface_1.TenantResolutionStrategy.PATH:
100
+ return new path_resolver_1.PathTenantResolver(tenantStore, options);
101
+ case multitenant_options_interface_1.TenantResolutionStrategy.CUSTOM:
102
+ if (!options.customResolver) {
103
+ throw new Error('Custom resolver must be provided when using CUSTOM strategy');
104
+ }
105
+ return new options.customResolver(tenantStore);
106
+ default:
107
+ throw new Error(`Unknown tenant resolution strategy: ${options.tenantResolutionStrategy}`);
108
+ }
109
+ }
110
+ };
111
+ exports.MultitenantModule = MultitenantModule;
112
+ exports.MultitenantModule = MultitenantModule = MultitenantModule_1 = __decorate([
113
+ (0, common_1.Module)({})
114
+ ], MultitenantModule);
115
+ //# sourceMappingURL=multitenant.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multitenant.module.js","sourceRoot":"","sources":["../src/multitenant.module.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { Request } from 'express';
2
+ import { TenantResolver, TenantStore } from '../interfaces/tenant-resolver.interface';
3
+ import { Tenant } from '../interfaces/tenant.interface';
4
+ import { MultitenantModuleOptions } from '../interfaces/multitenant-options.interface';
5
+ export declare class HeaderTenantResolver implements TenantResolver {
6
+ private readonly tenantStore;
7
+ private readonly options;
8
+ private readonly headerName;
9
+ constructor(tenantStore: TenantStore, options: MultitenantModuleOptions);
10
+ resolve(request: Request): Promise<Tenant | null>;
11
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.HeaderTenantResolver = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const constants_1 = require("../constants");
18
+ let HeaderTenantResolver = class HeaderTenantResolver {
19
+ constructor(tenantStore, options) {
20
+ this.tenantStore = tenantStore;
21
+ this.options = options;
22
+ this.headerName = options.headerName || constants_1.DEFAULT_HEADER_NAME;
23
+ }
24
+ async resolve(request) {
25
+ const tenantId = request.headers[this.headerName.toLowerCase()];
26
+ if (!tenantId) {
27
+ return null;
28
+ }
29
+ return this.tenantStore.findById(tenantId);
30
+ }
31
+ };
32
+ exports.HeaderTenantResolver = HeaderTenantResolver;
33
+ exports.HeaderTenantResolver = HeaderTenantResolver = __decorate([
34
+ (0, common_1.Injectable)(),
35
+ __param(0, (0, common_1.Inject)(constants_1.TENANT_STORE)),
36
+ __param(1, (0, common_1.Inject)(constants_1.MULTITENANT_OPTIONS)),
37
+ __metadata("design:paramtypes", [Object, Object])
38
+ ], HeaderTenantResolver);
39
+ //# sourceMappingURL=header.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header.resolver.js","sourceRoot":"","sources":["../../src/resolvers/header.resolver.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AAKpD,4CAAsF;AAO/E,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAG/B,YACyC,WAAwB,EACjB,OAAiC;QADxC,gBAAW,GAAX,WAAW,CAAa;QACjB,YAAO,GAAP,OAAO,CAA0B;QAE/E,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,+BAAmB,CAAC;IAC9D,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAgB;QAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,CAAW,CAAC;QAE1E,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC7C,CAAC;CACF,CAAA;AAnBY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;IAKR,WAAA,IAAA,eAAM,EAAC,wBAAY,CAAC,CAAA;IACpB,WAAA,IAAA,eAAM,EAAC,+BAAmB,CAAC,CAAA;;GALnB,oBAAoB,CAmBhC"}
@@ -0,0 +1,10 @@
1
+ import { Request } from 'express';
2
+ import { TenantResolver, TenantStore } from '../interfaces/tenant-resolver.interface';
3
+ import { Tenant } from '../interfaces/tenant.interface';
4
+ import { MultitenantModuleOptions } from '../interfaces/multitenant-options.interface';
5
+ export declare class PathTenantResolver implements TenantResolver {
6
+ private readonly tenantStore;
7
+ private readonly options;
8
+ constructor(tenantStore: TenantStore, options: MultitenantModuleOptions);
9
+ resolve(request: Request): Promise<Tenant | null>;
10
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.PathTenantResolver = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const constants_1 = require("../constants");
18
+ let PathTenantResolver = class PathTenantResolver {
19
+ constructor(tenantStore, options) {
20
+ this.tenantStore = tenantStore;
21
+ this.options = options;
22
+ }
23
+ async resolve(request) {
24
+ const path = request.path || request.url;
25
+ if (!path) {
26
+ return null;
27
+ }
28
+ const pathSegments = path.split('/').filter(Boolean);
29
+ if (pathSegments.length === 0) {
30
+ return null;
31
+ }
32
+ const tenantId = pathSegments[0];
33
+ return this.tenantStore.findById(tenantId);
34
+ }
35
+ };
36
+ exports.PathTenantResolver = PathTenantResolver;
37
+ exports.PathTenantResolver = PathTenantResolver = __decorate([
38
+ (0, common_1.Injectable)(),
39
+ __param(0, (0, common_1.Inject)(constants_1.TENANT_STORE)),
40
+ __param(1, (0, common_1.Inject)(constants_1.MULTITENANT_OPTIONS)),
41
+ __metadata("design:paramtypes", [Object, Object])
42
+ ], PathTenantResolver);
43
+ //# sourceMappingURL=path.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.resolver.js","sourceRoot":"","sources":["../../src/resolvers/path.resolver.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AAKpD,4CAAiE;AAO1D,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAC7B,YACyC,WAAwB,EACjB,OAAiC;QADxC,gBAAW,GAAX,WAAW,CAAa;QACjB,YAAO,GAAP,OAAO,CAA0B;IAC9E,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,OAAgB;QAC5B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC;QAEzC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,IAAI,CAAC;QACd,CAAC;QAID,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAErD,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;QAEjC,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC7C,CAAC;CACF,CAAA;AAzBY,gDAAkB;6BAAlB,kBAAkB;IAD9B,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,eAAM,EAAC,wBAAY,CAAC,CAAA;IACpB,WAAA,IAAA,eAAM,EAAC,+BAAmB,CAAC,CAAA;;GAHnB,kBAAkB,CAyB9B"}
@@ -0,0 +1,8 @@
1
+ import { Request } from 'express';
2
+ import { TenantResolver, TenantStore } from '../interfaces/tenant-resolver.interface';
3
+ import { Tenant } from '../interfaces/tenant.interface';
4
+ export declare class SubdomainTenantResolver implements TenantResolver {
5
+ private readonly tenantStore;
6
+ constructor(tenantStore: TenantStore);
7
+ resolve(request: Request): Promise<Tenant | null>;
8
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.SubdomainTenantResolver = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const constants_1 = require("../constants");
18
+ let SubdomainTenantResolver = class SubdomainTenantResolver {
19
+ constructor(tenantStore) {
20
+ this.tenantStore = tenantStore;
21
+ }
22
+ async resolve(request) {
23
+ const hostname = request.hostname || request.headers.host?.split(':')[0];
24
+ if (!hostname) {
25
+ return null;
26
+ }
27
+ const parts = hostname.split('.');
28
+ if (parts.length < 3) {
29
+ return null;
30
+ }
31
+ const subdomain = parts[0];
32
+ if (['www', 'api', 'admin', 'app'].includes(subdomain.toLowerCase())) {
33
+ return null;
34
+ }
35
+ return this.tenantStore.findBySubdomain(subdomain);
36
+ }
37
+ };
38
+ exports.SubdomainTenantResolver = SubdomainTenantResolver;
39
+ exports.SubdomainTenantResolver = SubdomainTenantResolver = __decorate([
40
+ (0, common_1.Injectable)(),
41
+ __param(0, (0, common_1.Inject)(constants_1.TENANT_STORE)),
42
+ __metadata("design:paramtypes", [Object])
43
+ ], SubdomainTenantResolver);
44
+ //# sourceMappingURL=subdomain.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subdomain.resolver.js","sourceRoot":"","sources":["../../src/resolvers/subdomain.resolver.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AAIpD,4CAA4C;AAOrC,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IAClC,YAAmD,WAAwB;QAAxB,gBAAW,GAAX,WAAW,CAAa;IAAG,CAAC;IAE/E,KAAK,CAAC,OAAO,CAAC,OAAgB;QAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAEzE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,IAAI,CAAC;QACd,CAAC;QAGD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAIlC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAG3B,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YACrE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IACrD,CAAC;CACF,CAAA;AA5BY,0DAAuB;kCAAvB,uBAAuB;IADnC,IAAA,mBAAU,GAAE;IAEE,WAAA,IAAA,eAAM,EAAC,wBAAY,CAAC,CAAA;;GADtB,uBAAuB,CA4BnC"}
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "nest-multitenant",
3
+ "version": "1.0.0",
4
+ "description": "Enterprise-grade multi-tenancy middleware for NestJS applications with support for multiple tenant resolution strategies and database isolation patterns",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "build:watch": "tsc --watch",
10
+ "test": "jest",
11
+ "test:watch": "jest --watch",
12
+ "test:cov": "jest --coverage",
13
+ "test:e2e": "jest --config ./test/jest-e2e.json",
14
+ "lint": "eslint \"{src,test}/**/*.ts\" --fix",
15
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
16
+ "prepublishOnly": "npm run build && npm test",
17
+ "prepare": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "nestjs",
21
+ "multitenancy",
22
+ "multi-tenant",
23
+ "saas",
24
+ "tenant",
25
+ "isolation",
26
+ "middleware",
27
+ "typescript",
28
+ "enterprise",
29
+ "database",
30
+ "typeorm",
31
+ "prisma",
32
+ "architecture"
33
+ ],
34
+ "author": {
35
+ "name": "Alireza Aminzadeh",
36
+ "email": "syeedalireza@yahoo.com",
37
+ "url": "https://github.com/syeedalireza"
38
+ },
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/syeedalireza/nest-multitenant.git"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/syeedalireza/nest-multitenant/issues"
46
+ },
47
+ "homepage": "https://github.com/syeedalireza/nest-multitenant#readme",
48
+ "peerDependencies": {
49
+ "@nestjs/common": "^10.0.0",
50
+ "@nestjs/core": "^10.0.0",
51
+ "reflect-metadata": "^0.1.13 || ^0.2.0",
52
+ "rxjs": "^7.8.0"
53
+ },
54
+ "dependencies": {
55
+ "uuid": "^9.0.1"
56
+ },
57
+ "devDependencies": {
58
+ "@nestjs/common": "^10.3.0",
59
+ "@nestjs/core": "^10.3.0",
60
+ "@nestjs/platform-express": "^10.3.0",
61
+ "@nestjs/testing": "^10.3.0",
62
+ "@nestjs/typeorm": "^10.0.1",
63
+ "@types/express": "^4.17.21",
64
+ "@types/jest": "^29.5.11",
65
+ "@types/node": "^20.11.0",
66
+ "@types/supertest": "^6.0.2",
67
+ "@types/uuid": "^9.0.7",
68
+ "@typescript-eslint/eslint-plugin": "^6.18.1",
69
+ "@typescript-eslint/parser": "^6.18.1",
70
+ "eslint": "^8.56.0",
71
+ "eslint-config-prettier": "^9.1.0",
72
+ "eslint-plugin-prettier": "^5.1.3",
73
+ "ioredis": "^5.3.2",
74
+ "jest": "^29.7.0",
75
+ "prettier": "^3.1.1",
76
+ "reflect-metadata": "^0.2.1",
77
+ "rxjs": "^7.8.1",
78
+ "supertest": "^6.3.4",
79
+ "ts-jest": "^29.1.1",
80
+ "ts-node": "^10.9.2",
81
+ "typeorm": "^0.3.19",
82
+ "typescript": "^5.3.3"
83
+ },
84
+ "optionalDependencies": {
85
+ "ioredis": "^5.3.2",
86
+ "typeorm": "^0.3.19"
87
+ },
88
+ "engines": {
89
+ "node": ">=18.0.0",
90
+ "npm": ">=9.0.0"
91
+ }
92
+ }