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: { reason } } }) 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);
const noteExecutor = 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”The write commands this feature provides (Provides).
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)