Built for infinite scale.
OVRIX is designed around strict tenant isolation at the database layer, event-driven decoupling at the service layer, and type-safe contracts everywhere. No hacks, no leaks, no excuses.
Schema-per-tenant
Every tenant receives a dedicated PostgreSQL schema named tenant_<slug>. Tables, indexes, and foreign keys are fully isolated. The shared schema holds only cross-tenant public data.
AsyncLocalStorage propagates the tenant context from the incoming JWT through every NestJS service, repository, and query builder — with zero manual context threading.
-- On tenant creation CREATE SCHEMA tenant_acme; -- Set search path per request SET search_path TO tenant_acme, public; -- Each tenant has isolated tables CREATE TABLE tenant_acme.employees ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id uuid NOT NULL REFERENCES public.tenants(id), email text UNIQUE NOT NULL, deleted_at timestamptz -- soft-delete );
JwtStrategy validates the bearer token, extracts user ID and tenant slug, attaches both to request.user.
TenantGuard reads the slug, resolves the tenant record from the public schema, and sets AsyncLocalStorage context before the controller runs.
Every TypeORM query builder call reads from AsyncLocalStorage and prepends the correct schema path. No manual joins, no WHERE tenant_id = everywhere.
BullMQ queues
Critical side-effects are decoupled into separate BullMQ queues backed by Redis. Producers emit events; consumers run in isolated worker processes. Retries, dead-letter queues, and job scheduling are built in.
// Emit from billing service await this.billingQueue.add('subscription.renewed', { tenantId: tenant.id, planId: subscription.planId, renewedAt: new Date(), }, { attempts: 3, backoff: { type: 'exponential', delay: 2000 }, }); // Consumer — notification worker @Processor('billing') export class BillingConsumer { @Process('subscription.renewed') async onRenewed(job: Job) { await this.mailer.sendRenewal(job.data); await this.kpiQueue.add('mrr.update', job.data); } }