Data residency

Pin each tenant's data to a HOME region and fail closed everywhere else — a deployment never serves or provisions a tenant homed in a region it doesn't hold, so a US deployment can never touch an EU-homed tenant's rows.

Data residency pins each tenant's data to a home region and makes every other deployment fail closed. A deployment declares which regions it can serve (it holds their stores); a request for a tenant homed elsewhere is refused, not served from a fallback — the gateway is expected to route it to the home region's deployment. So a US deployment can never read, bind, or provision an EU-homed tenant's data.

Residency never falls back to a default store. No home mapped → typed TenantResidencyUnresolved. Home region not served here → typed TenantRegionUnavailable. Both fail closed; that's the whole point.

Configure it

import { setResidencyConfig } from '@voltro/database'

setResidencyConfig({
  // tenant → home region (+ optional named connection for the home DB).
  homes: [
    { tenantId: 'acme',  region: 'eu-west' },
    { tenantId: 'globex', region: 'us-east' },
  ],
  // The regions THIS deployment can actually serve (it holds their stores).
  servableRegions: ['us-east'],
})

A tenant mapped to two different regions is rejected at config time (ambiguous routing), and servableRegions must be non-empty.

Resolve + bind per request

bindResidentStore picks the store for the subject's home region from the per-region handles this deployment holds — failing closed if the home is unresolved or not served here:

import { bindResidentStore } from '@voltro/database'

// stores: ReadonlyMap<region, StoreHandle> — what THIS deployment wired.
const { store, placement } = bindResidentStore(subject, config, stores)
// placement = { region, namespace, connectionKey? }
// throws TenantRegionUnavailable for an EU-homed tenant on a US deployment.

residentPlacement(subject, config) gives the placement alone (region + the tenant's namespace within that region's store) without needing the handles.

Provision a new resident tenant

provisionResidentTenant runs an injected provisioner against the tenant's HOME store + namespace, behind the SAME fail-closed guards as binding:

import { provisionResidentTenant } from '@voltro/database'

await provisionResidentTenant(subject, config, stores, async (store, placement) => {
  // e.g. provisionTenantNamespace(tables, placement.namespace, sqlLayer, dialect)
})
// A US deployment provisioning an EU-homed tenant → TenantRegionUnavailable.

How it composes with the tenant() mixin

Residency is the physical placement (which region's store); the tenant() mixin is the logical scope (the WHERE tenantId = … filter within a store). They stack: residency routes the request to the right region's store, then the mixin scopes the rows inside it. Namespace isolation within a region uses the same resolveTenantNamespace the mixin's physical-isolation mode uses.