Skip to content

Mount the foundation

Kumiko ships identity, auth, and multi-tenancy as bundled features. You do not wire login, user tables, or tenant schemas by hand. List your own features in one function and flip one flag for the schema CLI.

src/run-config.ts is the single source of truth for what your app mounts. The sample no longer exports a static APP_FEATURES array. It builds the list from tenant routing (base domain) so dev, prod, and schema generation stay aligned:

export function resolveBaseDomainFromEnv(): string {
return process.env["BASE_DOMAIN"] ?? "show-pony.localhost";
}
export function buildAppFeatures(routing: AppFeaturesRouting): FeatureDefinition[] {
return [
localeDe(),
authFoundationFeature,
createShowPonyTenantRoutingFeature({ baseDomain: routing.baseDomain }),
createTierEngineFeature({ defaultTier: DEFAULT_TIER, tierMap: SHOWPONY_TIER_MAP }),
billingFoundationFeature,
// mail, admin shell, app shell, showPonyFeature, …
];
}
export const HAS_AUTH = true;
From src/run-config.ts.

buildAppFeatures pulls in tenant routing, billing, tier limits, and the operational plugins (mail, jobs, audit, …) alongside showPonyFeature. HAS_AUTH = true is consumed by bin/kumiko.ts when generating schema: composeFeatures([...buildAppFeatures(...)], { includeBundled: HAS_AUTH }). At runtime, runDevApp / runProdApp call composeFeatures automatically when you pass an auth: block. That adds user, tenant, and email/password login without listing them in run-config.ts.

Login on the apex host dashboard
Email/password gate before the dashboard loads

After bun dev:

  • Email/password login at /login on the apex (show-pony.localhost:4180): chapter 12 adds the public landing on /
  • A seeded demo host: [email protected] / changeme
  • A seeded sysadmin: [email protected] / changeme (platform workspace): chapter 8)
  • Tenant-scoped writes and queries, with isolation from the framework rather than custom middleware
  • Admin shell (sidebar, topbar) ready for schema-driven screens in chapter 8

bin/server.ts spreads buildAppFeatures({ baseDomain: BASE_DOMAIN }) into runDevApp, seeds the demo admin, and sets up host routing (apex vs subdomain; detailed in chapter 4).

Demo events + RSVPs are seeded only in production (seedsDir in bin/main.ts, not on bun dev). Locally you create events in the dashboard or follow chapter 11.

// Single source of truth for show-pony feature composition.
// Both bin/server.ts (dev) and the kumiko-schema CLI build on this,
// so the runtime registry and generated schema never drift apart.
//
// HAS_AUTH=true → composeFeatures automatically pulls in the bundled auth chain
// (config/user/tenant/auth-email-password/secrets).
import { createAdminShellFeature } from "@cosmicdrift/kumiko-bundled-features/admin-shell";
import { createAuditFeature } from "@cosmicdrift/kumiko-bundled-features/audit";
import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
import { billingFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
import { createComplianceProfilesFeature } from "@cosmicdrift/kumiko-bundled-features/compliance-profiles";
import { createCryptoShreddingFeature } from "@cosmicdrift/kumiko-bundled-features/crypto-shredding";
import { createJobsFeature } from "@cosmicdrift/kumiko-bundled-features/jobs";
import { mailFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/mail-foundation";
import { mailTransportInMemoryFeature } from "@cosmicdrift/kumiko-bundled-features/mail-transport-inmemory";
import { createManagedPagesFeature } from "@cosmicdrift/kumiko-bundled-features/managed-pages";
import { createRateLimitingFeature } from "@cosmicdrift/kumiko-bundled-features/rate-limiting";
import { createSecretsFeature } from "@cosmicdrift/kumiko-bundled-features/secrets";
import { createSessionsFeature } from "@cosmicdrift/kumiko-bundled-features/sessions";
import { createTemplateResolverFeature } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
import { createTenantLifecycleFeature } from "@cosmicdrift/kumiko-bundled-features/tenant-lifecycle";
import { createTierEngineFeature } from "@cosmicdrift/kumiko-bundled-features/tier-engine";
import { composePagesStack } from "@cosmicdrift/kumiko-dev-server/compose-stacks";
import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
import { localeDe } from "@cosmicdrift/kumiko-locale-de";
import { appShellFeature } from "./features/app-shell/feature";
import { showPonyFeature } from "./features/show-pony/feature";
import { DEFAULT_TIER, SHOWPONY_TIER_MAP } from "./features/show-pony/tier-map";
import { renderLegalLayout } from "./legal-layout";
import { createShowPonyTenantRoutingFeature, resolveSubdomainPageTenant } from "./tenant-routing";
/** Overview screens + nav only — app-shell owns workspaces host/platform. */
const adminShellFeature = createAdminShellFeature({
registerWorkspaces: false,
includeTierAdmin: true,
});
export type AppFeaturesRouting = {
readonly baseDomain: string;
};
/** $BASE_DOMAIN or show-pony.localhost — the one place that default lives. */
export function resolveBaseDomainFromEnv(): string {
return process.env["BASE_DOMAIN"] ?? "show-pony.localhost";
}
export function buildAppFeatures(routing: AppFeaturesRouting): FeatureDefinition[] {
return [
localeDe(),
authFoundationFeature,
createSessionsFeature(),
createShowPonyTenantRoutingFeature({ baseDomain: routing.baseDomain }),
createTemplateResolverFeature(),
...composePagesStack({ wrapLayout: renderLegalLayout }),
createManagedPagesFeature({
resolveApexTenant: resolveSubdomainPageTenant,
allowCustomCss: false,
}),
mailFoundationFeature,
mailTransportInMemoryFeature,
createRateLimitingFeature(),
createAuditFeature(),
createJobsFeature(),
createTierEngineFeature({ defaultTier: DEFAULT_TIER, tierMap: SHOWPONY_TIER_MAP }),
createComplianceProfilesFeature(),
createTenantLifecycleFeature(),
billingFoundationFeature,
createCryptoShreddingFeature(),
createSecretsFeature(),
adminShellFeature,
appShellFeature,
showPonyFeature,
];
}
export const HAS_AUTH = true;

📄 On GitHub: src/run-config.ts