Skip to content

folders

Generic, host-agnostic hierarchical folders for any entity. Owns two event-sourced entities — the per-tenant folder tree (read_folders, self-referential via parentId) and SINGLE-membership folder-assignment rows keyed by (entityType, entityId) (read_folder_assignments) — so filing an entity adds NO column to the host and needs no relational pivot or JOIN. The folder catalog uses the generic entity handlers (create, update [= rename, optimistic-locked], delete, list, detail); set-folder puts/moves an entity into a folder (one folder per entity) and clear-folder unfiles it (both idempotent). Read which folder an entity is in, or which entities a folder holds, by listing folder-assignment filtered on entityId or folderId. Every path uses one access rule — adopt the host’s model with createFoldersFeature({ access: { openToAll: true } }) or pin roles. Pass { toggleable: { default: false } } to make the whole feature tier-gatable via the tier-engine (no host hook).

From recipes-tags-basic — the smallest working mount:

// kumiko-feature-version: 1
// Tags Basic Sample
//
// Shows the whole point of the `tags` bundle: tagging an entity needs ZERO
// changes to that entity. The `note` entity below has no tag column, no
// `wireTagsFor`, no awareness of tags at all — yet notes can be tagged and
// grouped, because the tags feature owns its own tables (read_tags +
// read_tag_assignments) and keys assignments by (entityType, entityId).
//
// Flow (see the integration test):
// 1. App-author defines a plain `note` entity — nothing tag-specific.
// 2. A tenant creates a tag via `tags:write:create-tag`.
// 3. The tag is attached to a note via `tags:write:assign-tag`
// with { tagId, entityType: "note", entityId: <noteId> }.
// 4. "Which tags does this note have?" / "Which notes carry this tag?"
// are read-layer compositions: list `tag-assignment` filtered by
// entityId or tagId — no JOIN, no column on `note`.
import { buildEntityTable, createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
import { createEntity, createTextField, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
import { z } from "zod";
// --- Entity ---
//
// A plain entity. Note there is NOTHING here that mentions tags — that is the
// feature's promise: any entity is taggable as-is.
export const noteEntity = createEntity({
table: "read_sample_tags_notes",
fields: {
title: createTextField({ required: true, maxLength: 200 }),
},
});
const noteTable = buildEntityTable("note", noteEntity);
function noteExecutor() {
return createEventStoreExecutor(noteTable, noteEntity, { entityName: "note" });
}
// --- Feature ---
export const noteFeature = defineFeature("note-management", (r) => {
// tags is non-optional for this recipe: the demo tags notes. The note
// feature itself stays completely tag-agnostic — it only declares the
// dependency so the bundle is mounted.
r.requires("tags");
r.entity("note", noteEntity);
r.writeHandler({
name: "note:create",
schema: z.object({ id: z.string(), title: z.string() }),
access: { roles: ["TenantAdmin"] },
handler: async (event, ctx) =>
noteExecutor().create(
{ id: event.payload.id, title: event.payload.title },
event.user,
ctx.db,
),
});
r.queryHandler({
name: "note:list",
schema: z.object({}),
access: { roles: ["TenantAdmin"] },
handler: async (_query, ctx) => {
const rows = await ctx.db.selectMany(noteTable);
return { rows };
},
});
});

📄 On GitHub: samples/recipes/tags-basic/src/feature.ts

folders feature preview

What this feature needs to run (Requires, top) and the write commands it provides (Provides, bottom).

flowchart TB
  n_folders["folders"]
  subgraph how_provides["Provides"]
    n_cmd_folders_write_clear_folder(["clear-folder"])
    n_cmd_folders_write_folder_create(["create"])
    n_cmd_folders_write_folder_delete(["delete"])
    n_cmd_folders_write_folder_update(["update"])
    n_cmd_folders_write_set_folder(["set-folder"])
  end
  n_folders --> n_cmd_folders_write_clear_folder
  n_folders --> n_cmd_folders_write_folder_create
  n_folders --> n_cmd_folders_write_folder_delete
  n_folders --> n_cmd_folders_write_folder_update
  n_folders --> n_cmd_folders_write_set_folder

Provides — write commands this feature registers (dispatch them through the command bus):

Start with recipes-tags-basic for a step-by-step walkthrough with runnable code and integration tests.

  • Requires: none
  • Activation: always on (not toggleable)