Data controller
A DataController is the backend data layer behind the DMS's data-driven components. It declares, once and with decorators, what each field of a record is and which operations exist — list, filter, sort, search, create/edit/delete, export, archive. From that single declaration the DMS generates the HTTP routes (each gated by its own permission), a TableView derives its columns and row actions (Tables), and the add/edit/view forms are built from the same fields (Forms).
DataController itself and the generic field decorators (@Listable, @Sortable, @Access, @Mandatory, …) are AntelopeJS concepts, not DMS ones — the framework's Data API guide and Database guide are their reference. The subject here is what the DMS layers on top, and how TableViews and forms derive from the controller.Usage
DataController(tableClass, routes, baseController) is a factory: it returns a base class that your controller extends. The subclass needs three things:
- the
@RegisterDataController()decorator on the class; - one
declareproperty per field — a field carries decorator metadata only, never a runtime value; - a model bound with
@ModelReference()+@Model(...).
import { Controller } from "@antelopejs/interface-api";
import { DataController, RegisterDataController } from "@antelopejs/interface-data-api";
import {
Access,
AccessMode,
Listable,
Mandatory,
ModelReference,
Sortable,
} from "@antelopejs/interface-data-api/metadata";
import { Model } from "@antelopejs/interface-database-decorators";
import { DefaultDataTypes } from "@antelopejs-private/cms/interfaces/cms-base/data-types";
import { Searchable } from "@antelopejs-private/cms/interfaces/cms-base/searchable";
import {
ArchiveField,
Column,
Exported,
Select,
TableViewRoutes,
} from "@antelopejs-private/cms/interfaces/cms-base/table-view";
import { Task, TaskModel } from "../db/task";
@RegisterDataController()
export class taskDataAPI extends DataController(
Task, // the @RegisterTable schema class
TableViewRoutes.All, // full CRUD + export + archive/restore
Controller("/api/task"), // the HTTP base path
) {
@ModelReference()
@Model(TaskModel) // bind the data model (an optional 2nd arg scopes it — see Multi-tenant data)
declare model: TaskModel;
@Listable()
@Exported()
@Access(AccessMode.ReadOnly)
declare _id: string;
@Searchable()
@Select()
@Listable()
@Sortable()
@Exported()
@Column({ name: "Name", type: new DefaultDataTypes.StringType(), filterable: true })
@Mandatory("new", "edit")
@Access(AccessMode.ReadWrite)
declare name: string;
@Listable()
@Exported()
@Column({
name: "Status",
type: new DefaultDataTypes.SelectType({
items: [
{ value: "pending", label: "Pending" },
{ value: "done", label: "Done" },
],
}),
filterable: true,
defaultValue: "pending",
})
@Mandatory("new", "edit")
@Access(AccessMode.ReadWrite)
declare status: string;
@ArchiveField()
@Access(AccessMode.ReadOnly)
declare isArchived: boolean;
}
To render this controller as a full CRUD screen, point a TableView at the class itself — see Tables.
Route generators (TableViewRoutes)
The second argument to DataController is a route map. TableViewRoutes.* wraps the generic data-api routes with the DMS's permission checks, validation, file handling, and realtime hooks; each route auto-registers its own permission.
| Route | Method | Permission | Purpose |
|---|---|---|---|
Get | GET /get | view | Single row by id (also acquires edit presence). |
List | GET | list | Paginated, filtered, sorted list. |
Select | GET | select (granted by default) | Lightweight list for pickers/relations (@Select fields). |
Count | GET | list | Total count only. |
New | POST | add | Create a row. |
Edit | PUT | edit | Update a row. |
Delete | DELETE | delete | Delete row(s). |
Archive / Restore | PUT | archive / restore | Bulk soft-archive / restore by ids. |
ExportRoutes | GET | export | exportStart / exportStatus / exportDownload. |
All | — | — | { get, list, select, count, new, edit, delete, archive, restore, …ExportRoutes }. |
Pass only the routes you want. A read-only controller passes { get, list, select, count } — no new/edit/delete, so a table on it renders no forms and those row actions disappear. The table's Export button appears only when all ExportRoutes keys are present.
Field decorators
A field's decorators decide which endpoints include it and how it behaves:
| Decorator | From | Marks the field as… |
|---|---|---|
@Column({ name, type, filterable?, group?, defaultValue?, readonlyBehavior?, isVisible?, order?, validate? }) | cms-base | A table column and a form field; type is a DataType instance. filterable: true wires per-column filtering; validation runs unless validate: false. |
@Listable(requiredFields?) | data-api | Included in the list result. For getter/joined fields, pass the DB fields they depend on, e.g. @Listable(["_instance"]). |
@Select(requiredFields?) | cms-base | Included in the select (picker) result — sugar for Listable(…, "select"). |
@Exported(requiredFields?) | cms-base | Included in the export result — sugar for Listable(…, "export"). |
@Searchable(compareMode?) | cms-base | Part of the global search box (default mode contains). Distinct from filterable. |
@Sortable({ noIndex? }) | data-api | Sortable. Use { noIndex: true } for getter/joined fields without a DB index. |
@Access(AccessMode.ReadOnly | ReadWrite | WriteOnly) | data-api | Read/write capability; read-only fields drop out of edit forms. |
@Mandatory("new", "edit") | data-api | Required in the listed form modes. |
@ArchiveField() | cms-base | The boolean soft-archive flag; auto-applies Filter() + Listable(). |
@ColumnGroup(id, config) + group: "<id>" | cms-base | Group fields under a heading in the form. |