Anonymous Access Multitenant
Anonymous access that still resolves a tenant.
What it shows
Section titled “What it shows”- Public write/read paths in a multi-tenant app
- Tenant resolution without a session user
Source
Section titled “Source”Feature entry point: src/feature.ts.
cd samples/recipes/anonymous-access-multitenantbun testSource code
Section titled “Source code”The feature entry point — embedded straight from the source file, so the code here is exactly what runs. Multi-file samples keep their remaining files next to it on GitHub (link below):
// Multi-Tenant Anonymous Access — Subdomain-Resolver Pattern//// Use case: SaaS deployment where every customer gets `<tenant>.shop.com`.// Anonymous visitors land on the right tenant via the host header. The// recipe shows the three pieces an app needs://// 1. tenantResolver — parses the Host header, looks up the tenantId in// a small cache + DB. Returns null on misses (404 to the visitor).// 2. tenantExists — guards header/cookie-supplied ids the same cache.// 3. setTenantCookie — once the resolver decided, persist on the visitor// so subsequent requests skip the parse + DB lookup.//// The product/order entities are minimal — the focus of this recipe is// the resolution chain, not the domain logic. Pair this with the// single-tenant recipe (samples/recipes/anonymous-access/) for// comparison.
import { buildEntityTable, createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";import { access, createEntity, createTextField, defineFeature,} from "@cosmicdrift/kumiko-framework/engine";import { z } from "zod";
export const productEntity = createEntity({ table: "mt_products", fields: { name: createTextField({ required: true }), },});
export const productTable = buildEntityTable("product", productEntity);
export const multiTenantShopFeature = defineFeature("mtshop", (r) => { r.entity("product", productEntity);
// Public listing — every tenant has its own products, scoped by tenantId // automatically via the framework's tenant-isolation. Anonymous visitors // see only the tenant they landed on. r.queryHandler( "product:list", z.object({}), async (_event, ctx) => ctx.db.selectMany(productTable), { access: { roles: [...access.anonymous, "User", "Admin"] } }, );
// Admin-only — proves role-gating still works on top of the multi-tenant // anonymous resolution. r.writeHandler( "product:create", z.object({ name: z.string().min(1) }), async (event, ctx) => { const exec = createEventStoreExecutor(productTable, productEntity, { entityName: "product", }); return exec.create(event.payload, event.user, ctx.db); }, { access: { roles: ["Admin"] } }, );});📄 On GitHub: samples/recipes/anonymous-access-multitenant/src/feature.ts