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 → typedTenantRegionUnavailable. 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.