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).
Quick example
Section titled “Quick example”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
Live preview
Section titled “Live preview”
How it fits
Section titled “How it fits”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):
folders:write:clear-folderfolders:write:folder:createfolders:write:folder:deletefolders:write:folder:updatefolders:write:set-folder
Getting started
Section titled “Getting started”Start with recipes-tags-basic for a step-by-step walkthrough with runnable code and integration tests.
Dependencies
Section titled “Dependencies”- Requires: none
- Activation: always on (not toggleable)