@helix/shared/interfaces
Shared TypeScript interfaces for the Helix platform.
Overview
This library provides interface definitions for object shapes used across all Helix microservices. Interfaces define the structure of complex data objects like request contexts, tenant information, and health check responses.
Installation
This is an internal library in the Nx monorepo. Import using the TypeScript path alias:
import { HealthCheckResponse, RequestContext, TenantBase } from '@helix/shared/interfaces';
Available Interfaces
Health Check
import { HealthCheckResponse, DependencyHealth } from '@helix/shared/interfaces';
const health: HealthCheckResponse = {
status: 'healthy',
service: 'auth-service',
mode: 'api',
version: '1.0.0',
timestamp: new Date().toISOString(),
uptime: 3600,
environment: 'production',
dependencies: {
postgres: { status: 'connected', responseTime: 5 },
redis: { status: 'connected', responseTime: 2 },
workos: { status: 'connected', responseTime: 120 }
}
};
Request Context
import { RequestContext } from '@helix/shared/interfaces';
// IMPORTANT: role and scopes come from WorkOS JWT, not from database
const context: RequestContext = {
requestId: 'req_abc123',
userId: 'user_01HXYZ',
tenantId: 'tenant_abc',
role: 'tenant_admin', // From JWT
scopes: ['tenant:read', 'tenant:write'], // From JWT
userEmail: 'user@example.com',
userDisplayName: 'John Doe',
timestamp: new Date(),
ip: '192.168.1.1',
userAgent: 'Mozilla/5.0...'
};
// Usage in NestJS controller
@Get('profile')
@UseGuards(JwtAuthGuard)
async getProfile(@CurrentUser() context: RequestContext) {
console.log(context.userId); // WorkOS user ID
console.log(context.role); // From JWT token
console.log(context.scopes); // From JWT token
}
Tenant Interfaces
import { TenantBase, TenantMember, TenantProduct } from '@helix/shared/interfaces';
// Tenant information (Central DB)
const tenant: TenantBase = {
id: 'tenant_abc123',
name: 'Acme Corporation',
domain: 'acme.com',
status: 'active',
settings: {
timezone: 'America/New_York',
dateFormat: 'MM/DD/YYYY'
},
createdAt: new Date('2025-01-15'),
updatedAt: new Date('2025-01-20')
};
// IMPORTANT: TenantMember has NO role field
// Roles are managed by WorkOS and included in JWT tokens
const member: TenantMember = {
id: 'tm_123',
tenantId: 'tenant_abc',
workosUserId: 'user_01HXYZ',
joinedAt: new Date('2025-01-15')
// NO role field - roles come from WorkOS JWT
};
// Product provisioning (Central DB)
const product: TenantProduct = {
id: 'tp_123',
tenantId: 'tenant_abc',
productType: 'kira',
status: 'active',
databaseHost: 'db.us-east-1.rds.amazonaws.com',
databaseName: 'tenant_abc123_kira',
databaseCredentials: 'encrypted_creds',
s3BucketPath: 'helix-prod-files/tenant_abc123/kira',
provisionedAt: new Date('2025-01-15')
};
Pagination Interfaces
import { PaginationParams, FilterParams } from '@helix/shared/interfaces';
// Pagination parameters
const pagination: PaginationParams = {
page: 1,
pageSize: 25,
sortBy: 'createdAt',
sortOrder: 'desc'
};
// Filter parameters
const filters: FilterParams = {
search: 'acme',
status: ['active', 'suspended'],
dateFrom: new Date('2025-01-01'),
dateTo: new Date('2025-12-31'),
customField: 'customValue' // Extensible for custom filters
};
// Usage in service
async findUsers(
pagination: PaginationParams,
filters: FilterParams
): Promise<PaginatedResponse<User>> {
const users = await this.userRepository.find({
skip: (pagination.page - 1) * pagination.pageSize,
take: pagination.pageSize,
where: {
email: filters.search ? { contains: filters.search } : undefined,
status: filters.status ? { in: filters.status } : undefined,
createdAt: {
gte: filters.dateFrom,
lte: filters.dateTo
}
},
orderBy: {
[pagination.sortBy || 'createdAt']: pagination.sortOrder || 'desc'
}
});
return {
data: users,
pagination: {
total: await this.userRepository.count(),
page: pagination.page,
pageSize: pagination.pageSize,
totalPages: Math.ceil(total / pagination.pageSize)
}
};
}
Audit Log
import { AuditLog } from '@helix/shared/interfaces';
const audit: AuditLog = {
id: 'audit_123',
tenantId: 'tenant_abc',
userId: 'user_01HXYZ',
action: 'update',
entityType: 'user',
entityId: 'user_456',
metadata: {
changes: {
firstName: { old: 'John', new: 'Jonathan' },
email: { old: 'john@old.com', new: 'john@new.com' }
},
reason: 'User profile update'
},
ipAddress: '192.168.1.1',
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...',
timestamp: new Date()
};
// Usage in audit service
async logAction(
context: RequestContext,
action: AuditAction,
entityType: AuditEntityType,
entityId: string,
metadata: Record<string, any>
) {
await this.auditRepository.create({
tenantId: context.tenantId,
userId: context.userId,
action,
entityType,
entityId,
metadata,
ipAddress: context.ip,
userAgent: context.userAgent,
timestamp: new Date()
});
}
WorkOS RBAC Architecture
CRITICAL DESIGN DECISION: User roles are NOT stored in our databases.
Why?
- Single Source of Truth: WorkOS manages all roles and permissions
- No Synchronization: No need to sync roles between WorkOS and our DB
- Performance: No database queries for role checks
- Security: Roles in signed JWT tokens (tamper-proof)
- Simplicity: Fewer moving parts, less code
How It Works
// 1. User authenticates via WorkOS
// 2. WorkOS returns JWT with roles and scopes
{
sub: 'user_01HXYZ',
tenantId: 'tenant_abc',
role: 'tenant_admin', // From WorkOS
scopes: ['tenant:write', ...], // From WorkOS
iat: 1705580000,
exp: 1705583600
}
// 3. Our guards validate the JWT and extract claims
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const user: RequestContext = request.user;
// Role comes from JWT, NOT from database query
return user.role === 'tenant_admin';
}
}
// 4. TenantMember in Central DB has NO role field
// It only maps workosUserId to tenantId for tenant selection
What About Product Access?
Product access is SEPARATE from roles:
- Roles (WorkOS): What you can do within a product
- Product Access (Tenant DB): Which products you can access
// Check 1: Does user have access to KIRA?
const access = await tenantDb.user_product_access.findFirst({
where: {
team_member: { workosUserId: context.userId },
product_type: 'kira',
revoked_at: null
}
});
if (!access) {
throw new ForbiddenException('No access to KIRA');
}
// Check 2: What can user do in KIRA?
// Role from JWT determines permissions
if (context.role === 'read_only') {
// Can only read
} else if (context.role === 'tenant_admin') {
// Can do anything
}
3-Tier Database Architecture
Interfaces map to our 3-tier database structure:
Tier 1: Central DB (Platform Metadata)
TenantBase- Tenant informationTenantMember- Minimal user-tenant mapping (NO PII, NO role)TenantProduct- Product provisioning recordsAuditLog- Platform-wide audit trail
Tier 2: Tenant DB (Per-Tenant Shared Data)
- Team members (with PII - email, name, avatar)
- Product access control
- Files (shared across products)
- Notifications (cross-product)
Tier 3: Product DBs (Per-Tenant-Product)
- KIRA: Threads, messages, assistant configs
- VERA: Reports, templates, questions
- CleverScreen: Screenings, monitoring jobs
Usage Patterns
In Controllers
import { RequestContext } from '@helix/shared/interfaces';
import { ApiResponse } from '@helix/shared/types';
@Controller('users')
export class UserController {
@Get(':id')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('tenant_admin', 'user')
async getUser(
@Param('id') userId: string,
@CurrentUser() context: RequestContext
): Promise<ApiResponse<User>> {
return this.userService.getUser(userId, context);
}
}
In Services
import { RequestContext, AuditLog } from '@helix/shared/interfaces';
import { AuditAction, AuditEntityType } from '@helix/shared/types';
@Injectable()
export class UserService {
async updateUser(
userId: string,
updates: Partial<User>,
context: RequestContext
): Promise<User> {
const user = await this.userRepository.update(userId, updates);
// Log the action
await this.auditService.log({
tenantId: context.tenantId,
userId: context.userId,
action: 'update',
entityType: 'user',
entityId: userId,
metadata: { updates },
ipAddress: context.ip,
userAgent: context.userAgent,
timestamp: new Date()
});
return user;
}
}
Related Libraries
@helix/shared/types- Type definitions and unions@helix/shared/constants- Constant values@helix/shared/utils- Utility functions- Central DB client (see Database & Prisma)
- Tenant DB client (see Database & Prisma)
Development
# Build the library
nx build shared-interfaces
# Lint the library
nx lint shared-interfaces
# Test the library
nx test shared-interfaces
License
Proprietary - CleverChain Limited