Tenant Config Generator
Drive per-tenant runtime configs and a shared tenant index from one typed tenant list.
Source
examples/tenant-config-generator/types.rank
pub Plan = `starter` | `growth` | `enterprise`
pub Region = `us-east-1` | `eu-west-1` | `ap-southeast-1`
pub Theme = `blueprint` | `sand` | `forest`
pub Tenant = Object {
id: string,
displayName: string,
plan: Plan,
region: Region,
domain: string,
theme: Theme,
}
pub TenantConfig = Object {
tenantId: string,
displayName: string,
baseUrl: string,
region: Region,
plan: Plan,
features: Object {
sso: bool,
auditLogs: bool,
customDomain: bool,
},
branding: Object {
theme: Theme,
logoPath: string,
},
}
examples/tenant-config-generator/main.rank
/// Generates per-tenant runtime configs plus a shared routing index.
/// Shows one tenant list driving multiple downstream artifacts.
use std::Emit
use std::Path
use std::collections::{ map }
use root::types::{ Plan, Tenant, TenantConfig }
tenants: [Tenant] = [
{
id: `atlas`,
displayName: `Atlas Health`,
plan: `starter`,
region: `us-east-1`,
domain: `atlas.example.com`,
theme: `blueprint`,
},
{
id: `helix`,
displayName: `Helix Manufacturing`,
plan: `growth`,
region: `eu-west-1`,
domain: `helix.example.eu`,
theme: `sand`,
},
{
id: `nova`,
displayName: `Nova Payments`,
plan: `enterprise`,
region: `ap-southeast-1`,
domain: `nova.example.io`,
theme: `forest`,
},
]
features_for = |plan: Plan| {
return match plan {
`starter` => {
sso: false,
auditLogs: false,
customDomain: true,
},
`growth` => {
sso: true,
auditLogs: false,
customDomain: true,
},
`enterprise` => {
sso: true,
auditLogs: true,
customDomain: true,
},
}
}
config_for = |tenant: Tenant| -> TenantConfig {
return {
tenantId: tenant.id,
displayName: tenant.displayName,
baseUrl: `https://${tenant.domain}`,
region: tenant.region,
plan: tenant.plan,
features: features_for(tenant.plan),
branding: {
theme: tenant.theme,
logoPath: `/branding/${tenant.id}.svg`,
},
}
}
tenant_configs = tenants |> map(|tenant: Tenant| config_for(tenant))
index = {
tenants: tenant_configs |> map(|config: TenantConfig| {
tenantId: config.tenantId,
domain: config.baseUrl,
region: config.region,
plan: config.plan,
}),
}
pub main = || Emit::manifest({
entries: [
{
path: Path::join([`tenants`, `atlas.json`]),
format: `json`,
value: tenant_configs[0],
},
{
path: Path::join([`tenants`, `helix.json`]),
format: `json`,
value: tenant_configs[1],
},
{
path: Path::join([`tenants`, `nova.json`]),
format: `json`,
value: tenant_configs[2],
},
{
path: Path::join([`tenants`, `index.json`]),
format: `json`,
value: index,
},
]
})
Output
Running the example with --file-root writes:
tenants/atlas.jsontenants/helix.jsontenants/nova.jsontenants/index.json
Run it
npm run rank -- examples/tenant-config-generator --file-root out/tenant-config-generator
Key concepts
- One typed tenant list drives multiple derived tenant-specific artifacts.
- Feature entitlements are computed from plan once and reused across all outputs.
- Shared data can emit both leaf artifacts and a cross-tenant routing index.