Skip to main content

@happyvertical/smrt-tags

Hierarchical tagging with context-scoped slugs, multi-language aliases, and slug utilities.

Installation

pnpm add @happyvertical/smrt-tags

Usage

import { Tag, TagCollection, TagAlias, TagAliasCollection } from '@happyvertical/smrt-tags';
import { sanitizeSlug, validateSlug, generateUniqueSlug } from '@happyvertical/smrt-tags';

// Create a tag hierarchy
const collection = await TagCollection.create();

// `collection.create(...)` returns the saved row, so use it directly
// instead of `new Tag()` + a separate `.create(...)` step. The returned
// instance has its persisted `id` set for use as a parent reference.
const tech = await collection.create({
slug: 'technology',
name: 'Technology',
context: 'blog', // defaults to 'global' if omitted
});

// Children reference their parent by UUID (`parentId`). With `tech`
// already saved, pass `tech.id` directly. `level` is recalculated by
// `TagCollection.moveTag` / `mergeTag` for later moves; you can pass it
// explicitly on create or let it stay at 0 and call moveTag later.
const ai = await collection.create({
slug: 'artificial-intelligence',
name: 'Artificial Intelligence',
context: 'blog',
parentId: tech.id,
level: 1,
});

// Traverse hierarchy
const children = await tech.getChildren();
const ancestors = await ai.getAncestors();

// Context scoping -- same slug, different contexts
const blogNews = new Tag({ slug: 'news', name: 'News', context: 'blog' });
const forumNews = new Tag({ slug: 'news', name: 'Forum News', context: 'forum' });

// Multi-language aliases
const aliasCollection = await TagAliasCollection.create();
const alias = new TagAlias({
tagSlug: 'technology',
alias: 'Tecnologia',
language: 'es',
context: 'blog',
});
await aliasCollection.create(alias);

// Collection operations. Slug args resolve through (slug, context) —
// pass an explicit context when the same slug exists in multiple
// contexts, otherwise the resolver throws on ambiguity.
await collection.moveTag('artificial-intelligence', 'technology', 'blog'); // cycle-checked
await collection.mergeTag('source', 'target', 'blog'); // moves children + aliases, deletes source
await collection.cleanupUnused(); // deletes tags with no children and no aliases

// Slug utilities
sanitizeSlug('My Cool Tag!'); // 'my-cool-tag'
validateSlug('my-tag-123'); // true

API

Models (SmrtObject)

ExportDescription
TagIdentified by slug + context. Hierarchical via parentId (UUID, inherited from SmrtHierarchical). Denormalised level recalculated by TagCollection.moveTag / mergeTag. JSON metadata. Hierarchy methods inherited: getParent(), getChildren(), getAncestors(), getDescendants(), getHierarchy(), moveTo(). Own helpers: getMetadata(), setMetadata(), updateMetadata()
TagAliasLanguage-specific translations: tagSlug, alias, language (ISO 639-1), optional context

Collections (SmrtCollection)

ExportDescription
TagCollectionCRUD + getOrCreate(), moveTag(), mergeTag(), cleanupUnused(), findWithGlobals()
TagAliasCollectionCRUD for tag aliases

Utilities

ExportDescription
sanitizeSlug(input)Clean and format slug strings
validateSlug(slug)Validate slug format (lowercase, alphanumeric + hyphens)
generateUniqueSlug(name, context, collection)Auto-numbered unique slug generation
calculateLevel(parentSlug, collection)Determine hierarchy depth from parent
hasCircularReference(slug, parentSlug, collection)Detect circular parent references

Types

ExportDescription
TagOptionsOptions for Tag constructor
TagAliasOptionsOptions for TagAlias constructor
TagMetadataFlexible metadata structure (colors, icons, statistics)
TagHierarchyComplete hierarchy result (ancestors, current, descendants)

Dependencies

PackagePurpose
@happyvertical/smrt-coreORM base (SmrtObject, SmrtCollection)
@happyvertical/smrt-tenancyOptional tenant scoping