Skip to main content

MULTI-TENANT POOL BYPASS

multi-tenant-pool-bypass.ts

ContextVar wrapper for cross-tenant lifespan/daemon code that runs outside the request lifecycle. Splits acquisition between tenant pool (RLS-enforced) and admin pool (cross-tenant).

Stark avatarStark

WHAT THIS PATTERN TEACHES

How to make pre-org-resolution and cross-tenant work mechanically explicit. Auth pre-resolution, system daemons, and admin endpoints need to bypass the tenant pool — the ContextVar makes the bypass legible and testable.

WHEN TO USE THIS

Any multi-tenant system with FORCE RLS on a non-owner runtime role. The tenant pool callback sets `app.current_org_id`; pre-resolution code needs a different acquisition path or it crashes with policy violations.

AT A GLANCE

await preOrgResolutionScope(async () => {
  const session = await db.query('SELECT org_id FROM sessions WHERE token = $1', [token]);
  return withTenant(session.org_id, () => handler(session));
});

FRAMEWORK IMPLEMENTATIONS

TypeScript
import { AsyncLocalStorage } from 'node:async_hooks';
import type { Pool, PoolClient } from 'pg';

type TenantContext = {
  org_id: number | null;       // null when in pre-resolution scope
  pre_resolution: boolean;     // true ⇒ acquire from admin pool, not tenant pool
};

const tenantContext = new AsyncLocalStorage<TenantContext>();

// ── Tenant scope (per-request, normal path) ──
export async function withTenant<T>(
  org_id: number,
  fn: () => Promise<T>,
): Promise<T> {
  return tenantContext.run({ org_id, pre_resolution: false }, fn);
}

// ── Pre-org-resolution scope (cross-tenant or auth lookup) ──
export async function preOrgResolutionScope<T>(fn: () => Promise<T>): Promise<T> {
← All Patterns