Skip to content

Feature Toggles

Runtime global feature toggles without reboot.

  • Dispatcher gate → feature_disabled when a feature is off
  • Cross-feature hooks skipped when a dependency toggle is off

Feature entry point: src/feature.ts.

Terminal window
cd samples/recipes/feature-toggles
bun test

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