@veap/taggable adds polymorphic, translatable tags to any model. Tags live in a shared tags table and are attached through a taggables pivot.
Install and migrate#
Run the migrations for @veap/translatable first (tag names/slugs are translated), then taggable:
import { runMigrations } from "@veap/database";
import { migrations as taggableMigrations } from "@veap/taggable/migrations";
await runMigrations("app", taggableMigrations);Make a model taggable#
import { TaggableModel } from "@veap/taggable";
export class BlogPost extends TaggableModel {
static override table = "blog_posts";
}Or, when your model already extends another base class:
import { Taggable } from "@veap/taggable";
import { TranslatableModel } from "@veap/translatable";
export class Article extends Taggable(TranslatableModel) {
static override table = "articles";
}Attach, detach and sync#
await post.attachTags(["typescript", "veap"]);
await post.attachTags(tagInstance); // attach a Tag instance
await post.detachTags("veap");
await post.detachTags(); // detach all
await post.syncTags(["orm", "knex"]); // replace the whole setString arguments are resolved by Tag.findOrCreateByName, which reuses an existing tag matched by translated name or slug and creates one otherwise. A UUID-looking string is treated as an existing tag id.
Query by tag#
const posts = await BlogPost.query().whereTag("typescript", "en").get();
const any = await BlogPost.query().whereAnyTag(["orm", "knex"], "en").get();
const all = await BlogPost.query().whereAllTags(["orm", "knex"], "en").get();
const withTags = await BlogPost.query().withTags().get(); // eager-loads tags.translationsUI#
@veap/taggable/ui exports TagInput, a client input for entering and suggesting tag names. value accepts names or Tag objects; onChange receives string[]. Use availableTags for suggestions and maxTags to cap the count.
Caveats#
- The tag helpers default to the
pllocale; pass the locale explicitly when your content is not Polish. attachTags/syncTagscreate missing tags as a side effect. Validate and normalize user input before persisting it, and consider a curation step so the tag table does not accumulate near-duplicates.