Inspected version: 0.1.0 (private) · Part of the veap-repo monorepo (packages/veap-categorizable).
@veap/categorizable adds hierarchical, polymorphic categorization to any @veap/database model. Categories live in a shared categories table (with translatable name/slug/description via @veap/translatable) and are attached through a categorizables pivot.
Entry points#
The manifest exports ., ./migrations, ./ui and ./package.json.
Models#
Category— extendsTranslatableModel(static table = "categories",morphAlias = "category",translatable = ["name", "slug", "description"]). Registered inMorphMapascategory.parent()(belongsTo) /children()(hasMany) — the hierarchy.Category.findOrCreateByName(name, parentId?, locale = "pl")— looks up by translated name or slug (optionally scoped to a parent) and creates the category when missing.slugifyCategory(text)— the slug helper.
CategoryFactory/createCategoryFactory()— a faker-backed factory withpl/entranslations.CategorySeeder— seeds 5 categories when the table is empty.
Mixins and base classes#
CategorizableModel— a base class extendingModel.Categorizable(Base)— the mixin form.ICategorizableModel/CategorizableClass— typing helpers.MorphOneCategory— a relation wrapper that exposes a single category from amorphToManyrelation (returns the first match;match()collapses the eager-loaded list to the first item).resolveCategoryIds(categories, locale = "pl")— resolves strings (names or UUIDs) andCategoryinstances to a de-duplicated id list.
Instance API#
| Method | Behavior |
|---|---|
categories() | The morphToMany(Category, "categorizable") relation. |
category() | A MorphOneCategory returning the first attached category. |
assignCategory(category, locale = "pl") | Resolves and attaches a category. |
syncCategories(categories, locale = "pl") | Replaces the attached set. |
detachCategories(categories?, locale = "pl") | Detaches the given categories, or all when omitted. |
Query builder#
CategorizableQueryBuilder extends ModelQueryBuilder:
whereCategory(nameOrSlugOrId, locale?)— matches by id when the value looks like a UUID, otherwise by translated name/slug.whereInCategories(namesOrSlugsOrIds, locale?)— matches any of the given ids or names/slugs.withCategories()— eager-loadscategories.translations.
Migrations#
The /migrations entry point creates the categories table (id, parent_id, timestamps) plus the polymorphic categorizables pivot (category_id, categorizable_id, categorizable_type, created_at) with a unique constraint and lookup indexes. down() preserves the shared tables.
UI (./ui)#
CategoryTreeSelect— a client tree selector.categoriesis a nestedCategoryItem[](id,name,parentId?,children?);value/onChangeuse category ids, withmultipletoggling single vs multi selection.
Runtime and caveats#
- Requires
@veap/translatablemigrations because category names/slugs are translated. category(),assignCategory/syncCategories/whereCategorydefault to the"pl"locale; pass the locale explicitly when needed.category()returns the first attached category; ordering of attached categories is not guaranteed by the relation, so do not rely on it for a "primary" category unless you enforce an order yourself.