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.
- package/LICENSE +21 -0
- package/README.md +343 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +11 -0
- package/dist/constants.js.map +1 -0
- package/dist/decorators/tenant.decorator.d.ts +4 -0
- package/dist/decorators/tenant.decorator.js +29 -0
- package/dist/decorators/tenant.decorator.js.map +1 -0
- package/dist/exceptions/tenant.exceptions.d.ts +10 -0
- package/dist/exceptions/tenant.exceptions.js +35 -0
- package/dist/exceptions/tenant.exceptions.js.map +1 -0
- package/dist/guards/tenant.guard.d.ts +4 -0
- package/dist/guards/tenant.guard.js +25 -0
- package/dist/guards/tenant.guard.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/multitenant-options.interface.d.ts +30 -0
- package/dist/interfaces/multitenant-options.interface.js +17 -0
- package/dist/interfaces/multitenant-options.interface.js.map +1 -0
- package/dist/interfaces/tenant-resolver.interface.d.ts +11 -0
- package/dist/interfaces/tenant-resolver.interface.js +3 -0
- package/dist/interfaces/tenant-resolver.interface.js.map +1 -0
- package/dist/interfaces/tenant.interface.d.ts +16 -0
- package/dist/interfaces/tenant.interface.js +3 -0
- package/dist/interfaces/tenant.interface.js.map +1 -0
- package/dist/middleware/tenant.middleware.d.ts +22 -0
- package/dist/middleware/tenant.middleware.js +100 -0
- package/dist/middleware/tenant.middleware.js.map +1 -0
- package/dist/multitenant.module.d.ts +11 -0
- package/dist/multitenant.module.js +115 -0
- package/dist/multitenant.module.js.map +1 -0
- package/dist/resolvers/header.resolver.d.ts +11 -0
- package/dist/resolvers/header.resolver.js +39 -0
- package/dist/resolvers/header.resolver.js.map +1 -0
- package/dist/resolvers/path.resolver.d.ts +10 -0
- package/dist/resolvers/path.resolver.js +43 -0
- package/dist/resolvers/path.resolver.js.map +1 -0
- package/dist/resolvers/subdomain.resolver.d.ts +8 -0
- package/dist/resolvers/subdomain.resolver.js +44 -0
- package/dist/resolvers/subdomain.resolver.js.map +1 -0
- 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
|
+
[](https://www.npmjs.com/package/nest-multitenant)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](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,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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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":";;;;;;;;;;;;;;;AAAA,2CAAoE;AAKpE,uEAGyC;AACzC,4CAAoE;AAkB7D,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAC3B,YAC4C,cAA8B,EAC1B,OAAiC;QADrC,mBAAc,GAAd,cAAc,CAAgB;QAC1B,YAAO,GAAP,OAAO,CAA0B;IAC9E,CAAC;IAEJ,KAAK,CAAC,GAAG,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;QAEvD,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAEtD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,IAAI,CAAC,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAC7C,CAAC;YAGD,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACrB,MAAM,IAAI,2CAAuB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC/C,CAAC;YAGD,GAAG,CAAC,aAAa,GAAG;gBAClB,MAAM;gBACN,MAAM,EAAE,IAAI,CAAC,mBAAmB,EAAE;gBAClC,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC;aAChC,CAAC;YAEF,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC;IAKO,gBAAgB,CAAC,IAAY;QACnC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzE,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE;YAEpD,IAAI,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9B,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBACxC,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;YACjC,CAAC;YACD,OAAO,IAAI,KAAK,WAAW,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC;IAKO,mBAAmB,CAAC,GAAY,EAAE,IAAkB;QAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,KAAK,KAAK,CAAC;QAEhE,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,IAAI,8CAA0B,EAAE,CAAC;QACzC,CAAC;QAGD,IAAI,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;YACjC,GAAG,CAAC,aAAa,GAAG;gBAClB,MAAM,EAAE;oBACN,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,eAAe;oBAChC,IAAI,EAAE,gBAAgB;oBACtB,QAAQ,EAAE,IAAI;iBACf;gBACD,MAAM,EAAE,QAAQ;gBAChB,QAAQ,EAAE,SAAS;aACpB,CAAC;QACJ,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC;IAKO,mBAAmB;QACzB,OAAO,IAAI,CAAC,OAAO,CAAC,wBAAmD,CAAC;IAC1E,CAAC;IAKO,WAAW,CAAC,GAAY;QAC9B,QAAQ,IAAI,CAAC,OAAO,CAAC,wBAAwB,EAAE,CAAC;YAC9C,KAAK,WAAW;gBACd,OAAO,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3C,KAAK,QAAQ;gBACX,OAAQ,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,WAAW,EAAE,IAAI,aAAa,CAAY,IAAI,EAAE,CAAC;YAChG,KAAK,MAAM;gBACT,OAAO,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACtC;gBACE,OAAO,EAAE,CAAC;QACd,CAAC;IACH,CAAC;CACF,CAAA;AAvGY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,eAAM,EAAC,2BAAe,CAAC,CAAA;IACvB,WAAA,IAAA,eAAM,EAAC,+BAAmB,CAAC,CAAA;;GAHnB,gBAAgB,CAuG5B"}
|
|
@@ -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
|
+
}
|