@vritti/api-sdk 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +504 -43
- package/dist/index.cjs +1242 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +755 -2
- package/dist/index.d.ts +755 -2
- package/dist/index.js +1222 -5
- package/dist/index.js.map +1 -1
- package/package.json +23 -3
package/README.md
CHANGED
|
@@ -1,44 +1,381 @@
|
|
|
1
1
|
# @vritti/api-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
NestJS SDK for multi-tenant applications with automatic database routing, JWT authentication, and request-scoped tenant context management.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@vritti/api-sdk)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 🏢 **Multi-tenant Database Management**: Automatic tenant routing with connection pooling
|
|
11
|
+
- 🔐 **JWT Authentication**: Built-in auth guard with refresh token validation
|
|
12
|
+
- 🌐 **Gateway & Microservice Support**: Optimized for both HTTP APIs and RabbitMQ workers
|
|
13
|
+
- 🎯 **Request-Scoped Context**: Tenant information available throughout the request lifecycle
|
|
14
|
+
- 🛡️ **Decorators**: `@Public()`, `@Onboarding()`, and `@Tenant()` for flexible access control
|
|
15
|
+
- ⚡ **Zero Configuration**: Auto-registers guards and interceptors
|
|
16
|
+
|
|
8
17
|
## Installation
|
|
9
18
|
|
|
10
19
|
```bash
|
|
11
20
|
# npm
|
|
12
|
-
npm install @vritti/api-sdk
|
|
21
|
+
npm install @vritti/api-sdk @nestjs/jwt @nestjs/config @prisma/client
|
|
13
22
|
|
|
14
23
|
# yarn
|
|
15
|
-
yarn add @vritti/api-sdk
|
|
24
|
+
yarn add @vritti/api-sdk @nestjs/jwt @nestjs/config @prisma/client
|
|
16
25
|
|
|
17
26
|
# pnpm
|
|
18
|
-
pnpm add @vritti/api-sdk
|
|
27
|
+
pnpm add @vritti/api-sdk @nestjs/jwt @nestjs/config @prisma/client
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### Gateway Mode (HTTP API)
|
|
33
|
+
|
|
34
|
+
For REST APIs and GraphQL gateways that serve HTTP requests:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { Module } from '@nestjs/common';
|
|
38
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
39
|
+
import { PrismaClient } from '@prisma/client';
|
|
40
|
+
import { AuthConfigModule, DatabaseModule } from '@vritti/api-sdk';
|
|
41
|
+
|
|
42
|
+
@Module({
|
|
43
|
+
imports: [
|
|
44
|
+
// Environment configuration
|
|
45
|
+
ConfigModule.forRoot({ isGlobal: true }),
|
|
46
|
+
|
|
47
|
+
// Multi-tenant database (Gateway mode)
|
|
48
|
+
DatabaseModule.forServer({
|
|
49
|
+
inject: [ConfigService],
|
|
50
|
+
useFactory: (config: ConfigService) => ({
|
|
51
|
+
primaryDb: {
|
|
52
|
+
host: config.get('PRIMARY_DB_HOST'),
|
|
53
|
+
port: config.get('PRIMARY_DB_PORT'),
|
|
54
|
+
username: config.get('PRIMARY_DB_USERNAME'),
|
|
55
|
+
password: config.get('PRIMARY_DB_PASSWORD'),
|
|
56
|
+
database: config.get('PRIMARY_DB_DATABASE'),
|
|
57
|
+
},
|
|
58
|
+
prismaClientConstructor: PrismaClient,
|
|
59
|
+
}),
|
|
60
|
+
}),
|
|
61
|
+
|
|
62
|
+
// JWT authentication
|
|
63
|
+
AuthConfigModule.forRootAsync(),
|
|
64
|
+
],
|
|
65
|
+
})
|
|
66
|
+
export class AppModule {}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Microservice Mode (RabbitMQ Workers)
|
|
70
|
+
|
|
71
|
+
For microservices that process messages from queues:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { Module } from '@nestjs/common';
|
|
75
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
76
|
+
import { PrismaClient } from '@prisma/client';
|
|
77
|
+
import { AuthConfigModule, DatabaseModule } from '@vritti/api-sdk';
|
|
78
|
+
|
|
79
|
+
@Module({
|
|
80
|
+
imports: [
|
|
81
|
+
ConfigModule.forRoot({ isGlobal: true }),
|
|
82
|
+
|
|
83
|
+
// Multi-tenant database (Microservice mode)
|
|
84
|
+
DatabaseModule.forMicroservice({
|
|
85
|
+
inject: [ConfigService],
|
|
86
|
+
useFactory: (config: ConfigService) => ({
|
|
87
|
+
prismaClientConstructor: PrismaClient,
|
|
88
|
+
}),
|
|
89
|
+
}),
|
|
90
|
+
|
|
91
|
+
AuthConfigModule.forRootAsync(),
|
|
92
|
+
],
|
|
93
|
+
})
|
|
94
|
+
export class AppModule {}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Environment Variables
|
|
98
|
+
|
|
99
|
+
### Required for All Modes
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
JWT_SECRET=your-access-token-secret-key
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Required for Gateway Mode
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Primary database (tenant registry)
|
|
109
|
+
PRIMARY_DB_HOST=localhost
|
|
110
|
+
PRIMARY_DB_PORT=5432
|
|
111
|
+
PRIMARY_DB_USERNAME=postgres
|
|
112
|
+
PRIMARY_DB_PASSWORD=postgres
|
|
113
|
+
PRIMARY_DB_DATABASE=vritti_primary
|
|
114
|
+
PRIMARY_DB_SCHEMA=public
|
|
115
|
+
|
|
116
|
+
# Optional
|
|
117
|
+
JWT_REFRESH_SECRET=your-refresh-token-secret-key
|
|
118
|
+
PRIMARY_DB_SSL_MODE=prefer # Options: require, prefer, disable
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Usage Examples
|
|
122
|
+
|
|
123
|
+
### Public Endpoints
|
|
124
|
+
|
|
125
|
+
Use `@Public()` to bypass authentication:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { Controller, Post, Body } from '@nestjs/common';
|
|
129
|
+
import { Public } from '@vritti/api-sdk';
|
|
130
|
+
|
|
131
|
+
@Controller('auth')
|
|
132
|
+
export class AuthController {
|
|
133
|
+
@Public()
|
|
134
|
+
@Post('login')
|
|
135
|
+
async login(@Body() dto: LoginDto) {
|
|
136
|
+
// No authentication required
|
|
137
|
+
return this.authService.login(dto);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Onboarding Endpoints
|
|
143
|
+
|
|
144
|
+
Use `@Onboarding()` for registration/verification flows:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { Controller, Post, Request } from '@nestjs/common';
|
|
148
|
+
import { Onboarding } from '@vritti/api-sdk';
|
|
149
|
+
|
|
150
|
+
@Controller('onboarding')
|
|
151
|
+
export class OnboardingController {
|
|
152
|
+
@Onboarding()
|
|
153
|
+
@Post('verify-email')
|
|
154
|
+
async verifyEmail(@Request() req) {
|
|
155
|
+
const userId = req.user.id; // Available from auth guard
|
|
156
|
+
return this.onboardingService.verifyEmail(userId);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
19
159
|
```
|
|
20
160
|
|
|
21
|
-
|
|
161
|
+
### Accessing Tenant Information
|
|
162
|
+
|
|
163
|
+
Use `@Tenant()` to inject tenant metadata:
|
|
22
164
|
|
|
23
165
|
```typescript
|
|
24
|
-
import {
|
|
166
|
+
import { Controller, Get, Post, Body } from '@nestjs/common';
|
|
167
|
+
import { Tenant, TenantInfo } from '@vritti/api-sdk';
|
|
168
|
+
|
|
169
|
+
@Controller('users')
|
|
170
|
+
export class UsersController {
|
|
171
|
+
@Get('info')
|
|
172
|
+
async getTenantInfo(@Tenant() tenant: TenantInfo) {
|
|
173
|
+
return {
|
|
174
|
+
id: tenant.id,
|
|
175
|
+
subdomain: tenant.subdomain,
|
|
176
|
+
type: tenant.type, // STARTER, PROFESSIONAL, ENTERPRISE
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@Post()
|
|
181
|
+
async createUser(
|
|
182
|
+
@Body() dto: CreateUserDto,
|
|
183
|
+
@Tenant() tenant: TenantInfo,
|
|
184
|
+
) {
|
|
185
|
+
this.logger.log(`Creating user for tenant: ${tenant.subdomain}`);
|
|
186
|
+
// Tenant-specific logic
|
|
187
|
+
if (tenant.type === 'ENTERPRISE') {
|
|
188
|
+
// Enable enterprise features
|
|
189
|
+
}
|
|
190
|
+
return this.usersService.create(dto);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Using Tenant Database Service
|
|
196
|
+
|
|
197
|
+
Access tenant-specific database connections:
|
|
25
198
|
|
|
26
|
-
|
|
27
|
-
|
|
199
|
+
```typescript
|
|
200
|
+
import { Injectable } from '@nestjs/common';
|
|
201
|
+
import { TenantDatabaseService } from '@vritti/api-sdk';
|
|
202
|
+
|
|
203
|
+
@Injectable()
|
|
204
|
+
export class UsersService {
|
|
205
|
+
constructor(
|
|
206
|
+
private readonly tenantDb: TenantDatabaseService,
|
|
207
|
+
) {}
|
|
208
|
+
|
|
209
|
+
async findAll() {
|
|
210
|
+
// Automatically uses tenant's database
|
|
211
|
+
const db = await this.tenantDb.getClient();
|
|
212
|
+
return db.user.findMany();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async create(data: CreateUserDto) {
|
|
216
|
+
const db = await this.tenantDb.getClient();
|
|
217
|
+
return db.user.create({ data });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
28
220
|
```
|
|
29
221
|
|
|
30
|
-
##
|
|
222
|
+
## Architecture
|
|
223
|
+
|
|
224
|
+
### Gateway Mode (`forServer()`)
|
|
225
|
+
|
|
226
|
+
**How it works:**
|
|
227
|
+
1. HTTP request arrives with tenant identifier (subdomain or `x-tenant-id` header)
|
|
228
|
+
2. `TenantContextInterceptor` extracts tenant identifier
|
|
229
|
+
3. `PrimaryDatabaseService` queries tenant registry for configuration
|
|
230
|
+
4. `VrittiAuthGuard` validates JWT tokens and tenant status
|
|
231
|
+
5. Tenant context is available throughout the request via `TenantContextService`
|
|
232
|
+
|
|
233
|
+
**Tenant Resolution:**
|
|
234
|
+
- Primary: Subdomain (`acme.api.vritti.com` → `acme`)
|
|
235
|
+
- Fallback: `x-tenant-id` header
|
|
31
236
|
|
|
32
|
-
### `
|
|
237
|
+
### Microservice Mode (`forMicroservice()`)
|
|
33
238
|
|
|
34
|
-
|
|
239
|
+
**How it works:**
|
|
240
|
+
1. RabbitMQ message arrives with embedded tenant information
|
|
241
|
+
2. `MessageTenantContextInterceptor` extracts tenant from message payload
|
|
242
|
+
3. Tenant context is set in `TenantContextService`
|
|
243
|
+
4. No primary database lookup needed (tenant info comes from gateway)
|
|
244
|
+
|
|
245
|
+
**Expected Message Format:**
|
|
246
|
+
```typescript
|
|
247
|
+
{
|
|
248
|
+
dto: { /* your data */ },
|
|
249
|
+
tenant: {
|
|
250
|
+
id: 'tenant-uuid',
|
|
251
|
+
subdomain: 'acme',
|
|
252
|
+
type: 'ENTERPRISE',
|
|
253
|
+
databaseHost: 'tenant-db.aws.com',
|
|
254
|
+
databaseName: 'acme_db',
|
|
255
|
+
// ... other config
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
35
259
|
|
|
36
|
-
|
|
260
|
+
## API Reference
|
|
261
|
+
|
|
262
|
+
### Modules
|
|
263
|
+
|
|
264
|
+
#### `DatabaseModule`
|
|
265
|
+
|
|
266
|
+
- **`forServer(options)`**: Configure for Gateway/HTTP mode
|
|
267
|
+
- **`forMicroservice(options)`**: Configure for RabbitMQ/messaging mode
|
|
268
|
+
|
|
269
|
+
#### `AuthConfigModule`
|
|
270
|
+
|
|
271
|
+
- **`forRootAsync()`**: Register JWT authentication with global guard
|
|
272
|
+
|
|
273
|
+
### Services
|
|
274
|
+
|
|
275
|
+
#### `TenantDatabaseService`
|
|
276
|
+
|
|
277
|
+
Access tenant-specific database connections.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
class TenantDatabaseService {
|
|
281
|
+
async getClient<T = any>(): Promise<T>
|
|
282
|
+
clearConnection(tenantId: string): void
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
#### `PrimaryDatabaseService`
|
|
287
|
+
|
|
288
|
+
Access the primary/platform database (tenant registry). Use this for cloud-api operations like managing tenants, users, sessions, etc.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
class PrimaryDatabaseService {
|
|
292
|
+
async getPrimaryDbClient<T = any>(): Promise<T>
|
|
293
|
+
async getTenantInfo(identifier: string): Promise<TenantInfo | null>
|
|
294
|
+
}
|
|
295
|
+
```
|
|
37
296
|
|
|
38
297
|
**Example:**
|
|
39
298
|
```typescript
|
|
40
|
-
|
|
41
|
-
|
|
299
|
+
@Injectable()
|
|
300
|
+
export class TenantRepository {
|
|
301
|
+
constructor(private readonly database: PrimaryDatabaseService) {}
|
|
302
|
+
|
|
303
|
+
async findAll() {
|
|
304
|
+
const prisma = await this.database.getPrimaryDbClient<PrismaClient>();
|
|
305
|
+
return prisma.tenant.findMany();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### `TenantContextService`
|
|
311
|
+
|
|
312
|
+
Manage request-scoped tenant context.
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
class TenantContextService {
|
|
316
|
+
getTenant(): TenantInfo
|
|
317
|
+
setTenant(tenant: TenantInfo): void
|
|
318
|
+
hasTenant(): boolean
|
|
319
|
+
clearTenant(): void
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Decorators
|
|
324
|
+
|
|
325
|
+
#### `@Public()`
|
|
326
|
+
|
|
327
|
+
Bypass authentication on specific endpoints.
|
|
328
|
+
|
|
329
|
+
#### `@Onboarding()`
|
|
330
|
+
|
|
331
|
+
Accept only onboarding tokens (for registration/verification flows).
|
|
332
|
+
|
|
333
|
+
#### `@Tenant()`
|
|
334
|
+
|
|
335
|
+
Inject tenant metadata into controller methods.
|
|
336
|
+
|
|
337
|
+
### Interfaces
|
|
338
|
+
|
|
339
|
+
#### `TenantInfo`
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
interface TenantInfo {
|
|
343
|
+
id: string;
|
|
344
|
+
subdomain: string;
|
|
345
|
+
type: 'STARTER' | 'PROFESSIONAL' | 'ENTERPRISE';
|
|
346
|
+
status: 'ACTIVE' | 'INACTIVE' | 'SUSPENDED';
|
|
347
|
+
databaseHost: string;
|
|
348
|
+
databasePort?: number;
|
|
349
|
+
databaseName: string;
|
|
350
|
+
databaseUsername: string;
|
|
351
|
+
databasePassword: string;
|
|
352
|
+
databaseSchema?: string;
|
|
353
|
+
sslMode?: 'require' | 'prefer' | 'disable';
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
#### `DatabaseModuleOptions`
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
interface DatabaseModuleOptions {
|
|
361
|
+
// Gateway mode only
|
|
362
|
+
primaryDb?: {
|
|
363
|
+
host: string;
|
|
364
|
+
port?: number;
|
|
365
|
+
username: string;
|
|
366
|
+
password: string;
|
|
367
|
+
database: string;
|
|
368
|
+
schema?: string;
|
|
369
|
+
sslMode?: 'require' | 'prefer' | 'disable';
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// Required for both modes
|
|
373
|
+
prismaClientConstructor: any;
|
|
374
|
+
|
|
375
|
+
// Optional
|
|
376
|
+
connectionCacheTTL?: number; // Default: 300000 (5 minutes)
|
|
377
|
+
maxConnections?: number; // Default: 10
|
|
378
|
+
}
|
|
42
379
|
```
|
|
43
380
|
|
|
44
381
|
## Development
|
|
@@ -47,11 +384,10 @@ const greeting = getHello();
|
|
|
47
384
|
|
|
48
385
|
- Node.js 18+
|
|
49
386
|
- Yarn
|
|
387
|
+
- PostgreSQL (for testing)
|
|
50
388
|
|
|
51
389
|
### Setup
|
|
52
390
|
|
|
53
|
-
Clone the repository and install dependencies:
|
|
54
|
-
|
|
55
391
|
```bash
|
|
56
392
|
git clone https://github.com/vritti-hub/api-sdk.git
|
|
57
393
|
cd api-sdk
|
|
@@ -60,14 +396,13 @@ yarn install
|
|
|
60
396
|
|
|
61
397
|
### Available Scripts
|
|
62
398
|
|
|
63
|
-
- `yarn dev` - Run
|
|
64
|
-
- `yarn build` - Build
|
|
65
|
-
- `yarn type-check` -
|
|
399
|
+
- `yarn dev` - Run in watch mode
|
|
400
|
+
- `yarn build` - Build for production
|
|
401
|
+
- `yarn type-check` - TypeScript type checking
|
|
66
402
|
- `yarn test` - Run tests
|
|
67
403
|
- `yarn test:watch` - Run tests in watch mode
|
|
68
|
-
- `yarn lint` - Lint source files
|
|
404
|
+
- `yarn lint` - Lint source files
|
|
69
405
|
- `yarn format` - Format code with Prettier
|
|
70
|
-
- `yarn format:check` - Check code formatting
|
|
71
406
|
- `yarn clean` - Remove build artifacts
|
|
72
407
|
|
|
73
408
|
### Project Structure
|
|
@@ -75,40 +410,166 @@ yarn install
|
|
|
75
410
|
```
|
|
76
411
|
api-sdk/
|
|
77
412
|
├── src/
|
|
78
|
-
│
|
|
79
|
-
├──
|
|
80
|
-
├──
|
|
81
|
-
|
|
82
|
-
├──
|
|
83
|
-
|
|
413
|
+
│ ├── auth/ # Authentication module
|
|
414
|
+
│ │ ├── guards/ # VrittiAuthGuard
|
|
415
|
+
│ │ ├── decorators/ # @Public, @Onboarding
|
|
416
|
+
│ │ └── auth-config.module.ts
|
|
417
|
+
│ ├── database/ # Database module
|
|
418
|
+
│ │ ├── services/ # Database services
|
|
419
|
+
│ │ ├── interceptors/ # Tenant context interceptors
|
|
420
|
+
│ │ ├── decorators/ # @Tenant
|
|
421
|
+
│ │ ├── interfaces/ # TypeScript interfaces
|
|
422
|
+
│ │ └── database.module.ts
|
|
423
|
+
│ ├── request/ # Request utilities (internal)
|
|
424
|
+
│ └── index.ts # Public API exports
|
|
425
|
+
├── dist/ # Build output
|
|
426
|
+
└── package.json
|
|
84
427
|
```
|
|
85
428
|
|
|
86
|
-
##
|
|
429
|
+
## Best Practices
|
|
87
430
|
|
|
88
|
-
|
|
431
|
+
### 1. Environment Variables
|
|
89
432
|
|
|
90
|
-
|
|
91
|
-
|
|
433
|
+
Always use `ConfigService` and validate environment variables at startup:
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
import { plainToClass } from 'class-transformer';
|
|
437
|
+
import { IsString, IsNumber, validateSync } from 'class-validator';
|
|
438
|
+
|
|
439
|
+
class EnvironmentVariables {
|
|
440
|
+
@IsString()
|
|
441
|
+
JWT_SECRET: string;
|
|
442
|
+
|
|
443
|
+
@IsString()
|
|
444
|
+
PRIMARY_DB_HOST: string;
|
|
445
|
+
|
|
446
|
+
@IsNumber()
|
|
447
|
+
PRIMARY_DB_PORT: number;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export function validate(config: Record<string, unknown>) {
|
|
451
|
+
const validatedConfig = plainToClass(EnvironmentVariables, config, {
|
|
452
|
+
enableImplicitConversion: true,
|
|
453
|
+
});
|
|
454
|
+
const errors = validateSync(validatedConfig, {
|
|
455
|
+
skipMissingProperties: false,
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
if (errors.length > 0) {
|
|
459
|
+
throw new Error(errors.toString());
|
|
460
|
+
}
|
|
461
|
+
return validatedConfig;
|
|
462
|
+
}
|
|
92
463
|
```
|
|
93
464
|
|
|
94
|
-
|
|
95
|
-
- `dist/index.js` - ESM build
|
|
96
|
-
- `dist/index.cjs` - CommonJS build
|
|
97
|
-
- `dist/index.d.ts` - TypeScript declarations for ESM
|
|
98
|
-
- `dist/index.d.cts` - TypeScript declarations for CJS
|
|
465
|
+
### 2. Database Connections
|
|
99
466
|
|
|
100
|
-
|
|
467
|
+
Let the SDK manage connection pooling. Don't create custom Prisma instances:
|
|
101
468
|
|
|
102
|
-
|
|
469
|
+
```typescript
|
|
470
|
+
// ✅ Good
|
|
471
|
+
@Injectable()
|
|
472
|
+
export class UsersService {
|
|
473
|
+
constructor(private readonly tenantDb: TenantDatabaseService) {}
|
|
474
|
+
|
|
475
|
+
async findAll() {
|
|
476
|
+
const db = await this.tenantDb.getClient();
|
|
477
|
+
return db.user.findMany();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// ❌ Bad - Don't do this
|
|
482
|
+
@Injectable()
|
|
483
|
+
export class UsersService {
|
|
484
|
+
private prisma = new PrismaClient(); // ❌ Breaks multi-tenancy
|
|
485
|
+
}
|
|
486
|
+
```
|
|
103
487
|
|
|
104
|
-
|
|
105
|
-
|
|
488
|
+
### 3. Tenant Context
|
|
489
|
+
|
|
490
|
+
Always use `@Tenant()` decorator instead of manually accessing `TenantContextService`:
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
// ✅ Good
|
|
494
|
+
@Get('info')
|
|
495
|
+
async getInfo(@Tenant() tenant: TenantInfo) {
|
|
496
|
+
return { subdomain: tenant.subdomain };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ❌ Bad - Avoid manual service injection
|
|
500
|
+
@Get('info')
|
|
501
|
+
async getInfo() {
|
|
502
|
+
const tenant = this.tenantContext.getTenant(); // ❌ Unnecessary
|
|
503
|
+
}
|
|
106
504
|
```
|
|
107
505
|
|
|
108
|
-
|
|
506
|
+
## Troubleshooting
|
|
109
507
|
|
|
110
|
-
|
|
111
|
-
|
|
508
|
+
### Issue: "TenantContextService not found"
|
|
509
|
+
|
|
510
|
+
**Cause:** DatabaseModule not imported or registered incorrectly.
|
|
511
|
+
|
|
512
|
+
**Solution:** Ensure `DatabaseModule.forServer()` or `forMicroservice()` is imported in your module.
|
|
513
|
+
|
|
514
|
+
### Issue: "JWT secret not configured"
|
|
515
|
+
|
|
516
|
+
**Cause:** Missing `JWT_SECRET` environment variable.
|
|
517
|
+
|
|
518
|
+
**Solution:** Add `JWT_SECRET` to your `.env` file.
|
|
519
|
+
|
|
520
|
+
### Issue: "Tenant identifier not found"
|
|
521
|
+
|
|
522
|
+
**Cause:** Request missing subdomain and `x-tenant-id` header.
|
|
523
|
+
|
|
524
|
+
**Solution:** Ensure requests include tenant identifier:
|
|
525
|
+
- Use subdomain: `https://acme.api.vritti.com`
|
|
526
|
+
- Or add header: `x-tenant-id: acme`
|
|
527
|
+
|
|
528
|
+
### Issue: "Connection pool exhausted"
|
|
529
|
+
|
|
530
|
+
**Cause:** Too many concurrent tenants or connections not released.
|
|
531
|
+
|
|
532
|
+
**Solution:** Increase `maxConnections` in DatabaseModule options:
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
DatabaseModule.forServer({
|
|
536
|
+
useFactory: () => ({
|
|
537
|
+
// ...
|
|
538
|
+
maxConnections: 20, // Increase from default 10
|
|
539
|
+
}),
|
|
540
|
+
})
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
## Migration Guide
|
|
544
|
+
|
|
545
|
+
### From Manual Setup to SDK
|
|
546
|
+
|
|
547
|
+
If you're migrating from a manual setup:
|
|
548
|
+
|
|
549
|
+
1. Remove manual interceptor registrations
|
|
550
|
+
2. Remove manual guard registrations
|
|
551
|
+
3. Replace custom tenant context with `@Tenant()` decorator
|
|
552
|
+
4. Update imports to use SDK exports
|
|
553
|
+
|
|
554
|
+
**Before:**
|
|
555
|
+
```typescript
|
|
556
|
+
@Module({
|
|
557
|
+
imports: [RequestModule],
|
|
558
|
+
providers: [
|
|
559
|
+
{ provide: APP_GUARD, useClass: VrittiAuthGuard },
|
|
560
|
+
{ provide: APP_INTERCEPTOR, useClass: TenantContextInterceptor },
|
|
561
|
+
],
|
|
562
|
+
})
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**After:**
|
|
566
|
+
```typescript
|
|
567
|
+
@Module({
|
|
568
|
+
imports: [
|
|
569
|
+
DatabaseModule.forServer({ /* config */ }),
|
|
570
|
+
AuthConfigModule.forRootAsync(),
|
|
571
|
+
],
|
|
572
|
+
})
|
|
112
573
|
```
|
|
113
574
|
|
|
114
575
|
## Contributing
|