Requirements
Documentation for Modules
Taxonomy Library - Requirements Specification
Library:
@libs-core/taxonomyLocation:libs/core/taxonomy/Type: Core infrastructure library (not a business module) Permission Namespace:taxonomy:*
1. Overview
Generic, reusable classification system that provides hierarchical categories and flexible tags to any module in the platform. Not tied to any specific domain — products, documents, tickets, knowledge articles, or any future entity can use taxonomy for organization.
1.1 Design Principles
- Domain-agnostic: No knowledge of what entities use it. Works via
entityTypediscriminator. - Multi-tenant: Full isolation per tenant.
- Embeddable: Provides UI components that any module can embed (category tree, tag selector).
- Event-driven: Publishes domain events for category/tag changes so consumers can react.
- Buildable: ng-packagr library for module boundary compliance (same as workflow-engine, media).
2. Functional Requirements
2.1 Category Entity
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | Unique identifier (UUID) |
tenantId |
string |
Yes | Multi-tenant isolation |
scope |
string |
Yes | Entity type scope (e.g., 'product', 'document', 'ticket'). Isolates category trees by domain. |
name |
string |
Yes | Category display name |
slug |
string |
Yes | URL-friendly identifier (unique per scope+tenant) |
description |
string |
No | Category description |
parentId |
string | null |
No | Parent category (null = root) |
sortOrder |
number |
No | Display order within parent |
isActive |
boolean |
Yes | Whether category is visible (default: true) |
imageId |
string |
No | Category image reference (via media system) |
level |
number |
Yes | Depth level (computed, 0 = root) |
path |
string |
Yes | Materialized path (e.g., /electronics/phones/smartphones) |
metadata |
Record<string, unknown> |
No | Extensible key-value metadata for domain-specific needs |
createdAt |
Date |
Yes | Creation timestamp |
updatedAt |
Date |
Yes | Last update timestamp |
Category Rules:
- Unlimited nesting depth
- Materialized path pattern for efficient ancestor/descendant querying
- Moving a category updates all descendant paths atomically
scopeisolates category trees —productcategories are completely separate fromdocumentcategories- Slug uniqueness enforced per
scope + tenantId + parentId - Cannot delete a category that has children (must move/delete children first)
2.2 Tag Entity
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | Unique identifier (UUID) |
tenantId |
string |
Yes | Multi-tenant isolation |
scope |
string |
Yes | Entity type scope (e.g., 'product', 'global') |
name |
string |
Yes | Tag display name |
slug |
string |
Yes | URL-friendly identifier (unique per scope+tenant) |
color |
string |
No | Display color (hex code) |
isActive |
boolean |
Yes | Whether tag is available (default: true) |
usageCount |
number |
Yes | Number of entities using this tag (maintained by consumers) |
createdAt |
Date |
Yes | Creation timestamp |
Tag Rules:
- Flat structure (no hierarchy)
scope: 'global'tags are available to all entity types- Tags can be created on-the-fly when assigning to entities (auto-create)
- Tag name uniqueness enforced per
scope + tenantId
3. Services
3.1 TaxonomyCategoryService
Injectable service for programmatic access to categories:
getTree(scope, tenantId, rootId?): Observable<CategoryTreeNode[]>— Full tree or subtreegetById(id): Observable<Category>— Single categorygetBySlug(scope, slug, tenantId): Observable<Category>— Lookup by sluggetChildren(parentId): Observable<Category[]>— Direct childrengetAncestors(categoryId): Observable<Category[]>— Path from root to categorycreate(category): Observable<Category>— Create categoryupdate(id, changes): Observable<Category>— Update categorymove(id, newParentId): Observable<Category>— Move to new parent (updates path)delete(id): Observable<void>— Delete (fails if has children)search(scope, query, tenantId): Observable<Category[]>— Search by name
3.2 TaxonomyTagService
Injectable service for programmatic access to tags:
getAll(scope, tenantId): Observable<Tag[]>— All tags for scopegetById(id): Observable<Tag>— Single taggetOrCreate(scope, name, tenantId): Observable<Tag>— Find by name or createcreate(tag): Observable<Tag>— Create tagupdate(id, changes): Observable<Tag>— Update tagdelete(id): Observable<void>— Delete tagsearch(scope, query, tenantId): Observable<Tag[]>— Search by nameincrementUsage(tagId): Observable<void>— Increment usage counterdecrementUsage(tagId): Observable<void>— Decrement usage counter
4. Integration Points
4.1 Domain Events
| Event | Payload | Trigger |
|---|---|---|
taxonomy.category.created |
{ category, scope, tenantId } |
Category created |
taxonomy.category.updated |
{ category, changes, scope, tenantId } |
Category updated |
taxonomy.category.moved |
{ categoryId, oldParentId, newParentId, scope, tenantId } |
Category moved |
taxonomy.category.deleted |
{ categoryId, scope, tenantId } |
Category deleted |
taxonomy.tag.created |
{ tag, scope, tenantId } |
Tag created |
taxonomy.tag.updated |
{ tag, changes, scope, tenantId } |
Tag updated |
taxonomy.tag.deleted |
{ tagId, scope, tenantId } |
Tag deleted |
4.2 Embeddable Components
| Component ID | Description | Inputs |
|---|---|---|
taxonomy:category-tree |
Interactive category tree with expand/collapse | scope, selectable, multiSelect, maxDepth, rootCategoryId, showInactive |
taxonomy:category-selector |
Dropdown/modal category picker | scope, multiSelect, placeholder, maxSelections |
taxonomy:tag-input |
Tag input with autocomplete and create-on-fly | scope, maxTags, allowCreate, placeholder |
taxonomy:tag-cloud |
Visual tag cloud display | scope, maxTags, showCount |
4.3 Public Actions
| Action ID | Description | Parameters | Permission |
|---|---|---|---|
taxonomy:get-categories |
Get category tree | { scope, parentId?, flat? } |
taxonomy:read |
taxonomy:get-tags |
Get tags list | { scope, query? } |
taxonomy:read |
taxonomy:create-category |
Create category | { category data } |
taxonomy:categories:manage |
taxonomy:create-tag |
Create tag | { tag data } |
taxonomy:tags:manage |
5. Permissions
| Permission | Description |
|---|---|
taxonomy:read |
View categories and tags |
taxonomy:categories:manage |
Create/edit/delete/move categories |
taxonomy:tags:manage |
Create/edit/delete tags |
6. User Stories
- US-01: As an admin, I can create a hierarchical category tree scoped to a specific entity type.
- US-02: As an admin, I can move a category to a different parent, with all descendants updating automatically.
- US-03: As an admin, I can create and manage tags scoped to a specific entity type.
- US-04: As a module developer, I can embed a category tree picker in my module's forms.
- US-05: As a module developer, I can embed a tag input with autocomplete in my module's forms.
- US-06: As a module developer, I can get/create categories and tags programmatically via services or public actions.
- US-07: As a module developer, I can listen to taxonomy events to react when categories or tags change.
7. Non-Functional Requirements
- NF-01: Buildable library (ng-packagr) for module boundary compliance.
- NF-02: All components standalone with OnPush change detection.
- NF-03: Multi-tenant isolation on all entities.
- NF-04: Full mock API for development.
- NF-05: Minimum 90% test coverage.
- NF-06: Category tree operations must handle deep hierarchies efficiently (materialized path).
8. Dependencies
@libs-core/taxonomy
├── @libs-core/event-system (domain events)
├── @libs-core/security (permissions, tenant context)
├── @libs-core/log-system (logging)
├── @libs-core/utilities (string utils, slug generation)
├── @libs-core/shared-models (BaseEntity, pagination, API types)
├── @libs-core/ui-framework (UI components for embeddables)
├── @libs-core/action-registry (public actions)
└── @libs-core/mock-api (development mock data)