Docs/Guides/ORM traits

ORM traits

Veap ships four optional packages that add common content capabilities to any @veap/database model:

3 min read

Veap ships four optional packages that add common content capabilities to any @veap/database model:

PackageCapabilityShared table
@veap/translatableMultilingual field valuestranslations
@veap/taggableFree-form tagstags + taggables
@veap/categorizableHierarchical categoriescategories + categorizables
@veap/commentableThreaded comments/reviewscomments

Each package is marked private in the monorepo and is consumed as a workspace dependency, not from the public registry.

Guides#

The shared design#

These packages all follow the same pattern:

  1. One shared table per capability. Translatable stores every model's translations in a single translations table; taggable and categorizable store the taxonomy in tags/categories plus a polymorphic pivot; commentable stores every model's comments in one comments table. The owner is identified by a *_type/*_id pair (a morphMany, morphToMany or morphTo relation).
  2. A mixin and a base class. You either extend the provided *Model base class or wrap an existing class with the *() mixin — useful when your model already extends another base.
  3. A scoped query builder. Each trait returns a *QueryBuilder from static query() so you get whereTranslation, whereTag, whereCategory and whereHasComments-style helpers and eager loading for free.
  4. A MorphMap registration. Each model registers its morph alias (tag, category, comment, …) so polymorphic relations can resolve the target class. Register the aliases your models use.
  5. Migrations that do not drop shared tables. The down() functions intentionally keep the shared tables, because independent packages share them; each owner cleans up its own rows.
  6. Optional UI components. Each package exposes client components under ./ui for editors (a translatable form, a tag input, a category tree, a comment section).

Migrations#

Run the trait migrations through the host application's migration flow. See the migrations guide and register them with withMigrations([...]) or your own runner. The dependency order is:

typescript
translations  (required by taggable and categorizable)
tags + taggables
categories + categorizables
comments

Because the tables are shared, a plugin rollback keeps them; delete rows by owner instead.

Locale defaults#

The taggable and categorizable helpers default to the pl locale, and the locale manager in translatable defaults to en, while the translatable UI components default to ["pl", "en"]. These defaults differ across packages — set the locale explicitly whenever the value matters.

Security and correctness#

  • A moderation status on a comment is data, not authorization. Enforce who may read, create or moderate content with server-side checks.
  • Guest-provided values (names, emails, comment bodies, tag/category names) are untrusted input: validate length, sanitize rendered output, and never interpolate them into raw SQL.
  • These traits use the database layer's writes directly. If your application requires every write to run inside transaction(), wrap the trait method calls yourself.
  • Slug helpers generate slugs from arbitrary text; enforce uniqueness in your own code if a duplicate slug would break routing.
veap
Preparing0%