Tags Domain
The tags domain provides flexible resource tagging and categorization for TypeScript backends. Tag any resource type without coupling, using a generic junction model. It is structured in strict Clean Architecture layers with zero npm dependencies in the domain and application layers.
Install
Section titled “Install”npx @backcap/cli add tagsDomain Model
Section titled “Domain Model”Tag Entity
Section titled “Tag Entity”The Tag entity is the aggregate root. It auto-generates a slug from the tag name.
import { Tag } from "./domains/tags/domain/entities/tag.entity";
const result = Tag.create({ id: crypto.randomUUID(), name: "Web Development" });
if (result.isOk()) { const tag = result.unwrap(); console.log(tag.slug.value); // "web-development"}| Field | Type | Description |
|---|---|---|
id | string | Unique identifier (UUID) |
name | string | Human-readable tag name |
slug | TagSlug | Auto-generated kebab-case slug |
createdAt | Date | Timestamp of creation |
TagSlug Value Object
Section titled “TagSlug Value Object”import { TagSlug } from "./domains/tags/domain/value-objects/tag-slug.vo";
// Create from explicit slugconst result = TagSlug.create("web-development");
// Auto-generate from nameconst generated = TagSlug.fromName("Web Development");// "web-development"Validates: lowercase kebab-case, 1–64 characters, no leading or trailing hyphens. fromName() auto-generates a slug from any string.
Domain Errors
Section titled “Domain Errors”| Error Class | Condition | Message |
|---|---|---|
TagNotFound | No tag found for the given slug | Tag not found with slug: "<slug>" |
TagAlreadyExists | A tag with the same slug already exists | Tag already exists with slug: "<slug>" |
ResourceTagNotFound | Resource is not tagged | Resource tag not found: tag "<slug>" is not associated with... |
ResourceAlreadyTagged | Resource is already tagged | Resource already tagged: tag "<slug>" is already associated with... |
InvalidTagSlug | Slug fails validation | Invalid tag slug: "<slug>" |
Domain Events
Section titled “Domain Events”| Event | Emitted By | Payload |
|---|---|---|
TagCreated | CreateTag use case | tagId, slug, occurredAt |
Application Layer
Section titled “Application Layer”Use Cases
Section titled “Use Cases”CreateTag
Section titled “CreateTag”Registers a new tag with auto-generated slug. Fails if a tag with the same slug already exists.
const result = await tagsService.createTag({ name: "TypeScript" });// Result<{ tagId: string; slug: string; createdAt: Date }, Error>TagResource
Section titled “TagResource”Associates a tag with any resource. Fails if the resource is already tagged.
const result = await tagsService.tagResource({ tagSlug: "typescript", resourceId: "post-123", resourceType: "post",});// Result<{ taggedAt: Date }, Error>UntagResource
Section titled “UntagResource”Removes a tag association. Fails if the association doesn’t exist.
const result = await tagsService.untagResource({ tagSlug: "typescript", resourceId: "post-123", resourceType: "post",});// Result<{ untaggedAt: Date }, Error>ListByTag
Section titled “ListByTag”Finds resources tagged with a specific tag.
const result = await tagsService.listByTag({ tagSlug: "typescript", resourceType: "post", // optional filter limit: 50, offset: 0,});// Result<{ resources: Array<{ resourceId, resourceType, taggedAt }>; total: number }, Error>Port Interfaces
Section titled “Port Interfaces”ITagRepository
Section titled “ITagRepository”export interface ITagRepository { saveTag(tag: Tag): Promise<void>; findBySlug(slug: string): Promise<Tag | undefined>; tagResource(tagId: string, resourceId: string, resourceType: string): Promise<void>; untagResource(tagId: string, resourceId: string, resourceType: string): Promise<void>; findResourcesByTag(tagId: string, filters: TagResourceFilters): Promise<{ resources: Array<...>; total: number }>; isResourceTagged(tagId: string, resourceId: string, resourceType: string): Promise<boolean>;}Public API (contracts/)
Section titled “Public API (contracts/)”import { createTagsService, ITagsService } from "./domains/tags/contracts";
const tagsService: ITagsService = createTagsService({ tagRepository });File Map
Section titled “File Map”domains/tags/ domain/ entities/tag.entity.ts value-objects/tag-slug.vo.ts events/tag-created.event.ts errors/tag-not-found.error.ts errors/resource-tag-not-found.error.ts errors/invalid-tag-slug.error.ts errors/tag-already-exists.error.ts errors/resource-already-tagged.error.ts application/ use-cases/create-tag.use-case.ts use-cases/tag-resource.use-case.ts use-cases/untag-resource.use-case.ts use-cases/list-by-tag.use-case.ts ports/tag-repository.port.ts dto/create-tag.dto.ts dto/tag-resource.dto.ts dto/untag-resource.dto.ts dto/list-by-tag.dto.ts contracts/ tags.contract.ts tags.factory.ts index.ts shared/ result.ts