@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 CHANGED
@@ -1,44 +1,381 @@
1
1
  # @vritti/api-sdk
2
2
 
3
- A TypeScript SDK for interacting with Vritti APIs.
3
+ NestJS SDK for multi-tenant applications with automatic database routing, JWT authentication, and request-scoped tenant context management.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@vritti/api-sdk.svg)](https://www.npmjs.com/package/@vritti/api-sdk)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- ## Usage
161
+ ### Accessing Tenant Information
162
+
163
+ Use `@Tenant()` to inject tenant metadata:
22
164
 
23
165
  ```typescript
24
- import { getHello } from '@vritti/api-sdk';
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
- const message = getHello();
27
- console.log(message); // "Hello, World!"
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
- ## API Documentation
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
- ### `getHello()`
237
+ ### Microservice Mode (`forMicroservice()`)
33
238
 
34
- Returns a greeting message.
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
- **Returns:** `string` - A hello world message
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
- const greeting = getHello();
41
- // Returns: "Hello, World!"
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 the SDK in watch mode using tsx
64
- - `yarn build` - Build the SDK for production (outputs CJS and ESM formats)
65
- - `yarn type-check` - Run TypeScript type checking
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 with ESLint
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
- └── index.ts # Main entry point
79
- ├── dist/ # Build output (generated)
80
- ├── .prettierrc # Prettier configuration
81
- ├── eslint.config.js # ESLint configuration
82
- ├── tsconfig.json # TypeScript configuration
83
- └── package.json # Package configuration
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
- ## Building
429
+ ## Best Practices
87
430
 
88
- The SDK is built using [tsup](https://tsup.egoist.dev/) which generates both CommonJS and ESM outputs with TypeScript declarations:
431
+ ### 1. Environment Variables
89
432
 
90
- ```bash
91
- yarn build
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
- This will create:
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
- ## Testing
467
+ Let the SDK manage connection pooling. Don't create custom Prisma instances:
101
468
 
102
- Run the test suite:
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
- ```bash
105
- yarn test
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
- Run tests in watch mode during development:
506
+ ## Troubleshooting
109
507
 
110
- ```bash
111
- yarn test:watch
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