Feature Toggles
Runtime global feature toggles without reboot.
What it shows
Section titled “What it shows”- Dispatcher gate →
feature_disabledwhen a feature is off - Cross-feature hooks skipped when a dependency toggle is off
Source
Section titled “Source”Feature entry point: src/feature.ts.
cd samples/recipes/feature-togglesbun 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):
// Feature-Toggles Showcase — two toggleable features that prove the two// operator-visible gate points in one test://// 1. Dispatcher-gate on the owner feature's handler (403 feature_disabled)// 2. Hook-filter on a cross-feature r.hook({ allOf }) (skipped when hook-owner off)//// The feature-toggles bundled-feature itself is loaded in the integration// test so the canonical wiring (runtime accessor + effectiveFeatures// callback + set-handler) is exercised as documentation, not just in the// framework's own tests.
import { buildEntityTable, createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";import { createBooleanField, createEntity, createSystemUser, createTextField, defineFeature, type FeatureDefinition, SYSTEM_TENANT_ID,} from "@cosmicdrift/kumiko-framework/engine";import { InternalError } from "@cosmicdrift/kumiko-framework/errors";import { z } from "zod";
// product — toggleable, default on. Owns the `product` entity and a// create-handler. Wire-path: event-store executor → projection table →// lifecycle pipeline (that's where the cross-feature hook below gets// invoked from).export const productEntity = createEntity({ table: "read_products", fields: { name: createTextField({ required: true, maxLength: 100 }), active: createBooleanField({ default: true }), },});export const productTable = buildEntityTable("product", productEntity);const productCrud = createEventStoreExecutor(productTable, productEntity, { entityName: "product",});
export function createProductFeature(): FeatureDefinition { return defineFeature("product", (r) => { r.systemScope(); r.toggleable({ default: true }); r.entity("product", productEntity); r.writeHandler( "product:create", z.object({ name: z.string().min(1).max(100) }), async (event, ctx) => { if (!ctx.systemDb) { throw new InternalError({ message: "product:create requires ctx.systemDb — is r.systemScope() still set?", }); } const db = ctx.systemDb.acknowledgeCrossTenant( "product catalog is system-wide, not tenant-scoped", ); return productCrud.create(event.payload, event.user, db); }, { access: { roles: ["SystemAdmin"] } }, ); });}
// product-audit — toggleable, default on. Registers a cross-feature// r.hook({ allOf: "product" }) on product's postSave. When this feature is globally off,// the hook is silently skipped; product's own write-handler keeps working.// When product itself is off, the handler is gated before any write// happens — the hook never has anything to react to.export const productAuditEntity = createEntity({ table: "read_product_audits", fields: { productName: createTextField({ required: true, maxLength: 100 }), },});export const productAuditTable = buildEntityTable("product-audit", productAuditEntity);const productAuditCrud = createEventStoreExecutor(productAuditTable, productAuditEntity, { entityName: "product-audit",});
export function createProductAuditFeature(): FeatureDefinition { return defineFeature("product-audit", (r) => { r.systemScope(); r.toggleable({ default: true }); r.entity("product-audit", productAuditEntity);
r.hook("postSave", { allOf: "product" }, async (result, ctx) => { if (result.kind !== "save" || !result.isNew) return; if (!ctx.systemDb) return; const name = (result.changes as Record<string, unknown>)["name"] as string | undefined; if (!name) return; const db = ctx.systemDb.acknowledgeCrossTenant( "audit sink is system-wide, mirrors the system-wide product catalog", ); // Cross-feature audit sink is itself an r.entity projection — write it via // the executor (system actor, no human caller) so the row is event-backed // and survives a rebuild, not a direct method-form insert. await productAuditCrud.create({ productName: name }, createSystemUser(SYSTEM_TENANT_ID), db); }); });}📄 On GitHub: samples/recipes/feature-toggles/src/feature.ts