Imported from AntelopeJS/interface-data-api (
skills/data-api-interface/SKILL.md). Install upstream withnpx skills add AntelopeJS/interface-data-api --skill data-api-interface. Copyright stays with the author.
Data API Interface
Declarative CRUD layer on top of @antelopejs/interface-api (HTTP) and
@antelopejs/interface-database(-decorators) (storage). You build a controller class with
DataController(tableClass, routes, Controller("/path")), decorate fields to expose them, and get
get/list/new/edit/delete routes generated. Everything here is consumer-side: there is no proxy
point of its own to implement — the underlying api/database interfaces do the actual crossings, so
your module must also import those peer interfaces.
Imports
import {
DataController,
DefaultRoutes,
RegisterDataController,
GetDataControllerMeta,
} from "@antelopejs/interface-data-api";
import {
Access,
AccessMode,
Listable,
Mandatory,
Optional,
Sortable,
Filter,
Foreign,
Joined,
Computed,
Validator,
ModelReference,
ModifierKey,
} from "@antelopejs/interface-data-api/metadata";
import {
Parameters,
Query,
Validation,
} from "@antelopejs/interface-data-api/components";
/components is only needed for custom route callbacks (query building, validation helpers).
Minimal consumption example
import { Controller } from "@antelopejs/interface-api";
import {
DataController,
DefaultRoutes,
RegisterDataController,
} from "@antelopejs/interface-data-api";
import {
Access,
AccessMode,
Listable,
Mandatory,
ModelReference,
Sortable,
} from "@antelopejs/interface-data-api/metadata";
import {
BasicDataModel,
Field,
Model,
RegisterTable,
Table,
} from "@antelopejs/interface-database-decorators";
@RegisterTable("tasks", "default")
class Task extends Table {
declare _id: string;
@Field("string")
declare summary: string;
}
class TaskModel extends BasicDataModel(Task, "tasks") {}
@RegisterDataController()
class TaskAPI extends DataController(
Task,
DefaultRoutes.All,
Controller("/tasks"),
) {
@ModelReference()
@Model(TaskModel, "my-database")
declare taskModel: TaskModel;
@Listable()
@Access(AccessMode.ReadOnly)
declare _id: string;
@Listable()
@Sortable()
@Mandatory("new")
@Access(AccessMode.ReadWrite)
declare summary: string;
}
This exposes GET /tasks/get?id=, GET /tasks/list, POST /tasks/new, PUT /tasks/edit?id=,
DELETE /tasks/delete?id= (repeat id to delete several).
Gotchas
@RegisterDataController()is mandatory: it wires field decorators, parameter providers, and routes. A@ModelReference()property holding a@Modelinstance is required too (Query.GetModelthrows 500 without it). A controller derived from another data controller inherits it unless it declares its own.- Fields with no decorator are invisible to the API — neither returned nor writable; visibility
requires
@Access.@Optional()only registers a field as not-mandatory (it does not expose it). listresponds{ results, total, offset, limit }.limitdefaults to 10 and is capped at 100 (maxPage). Sorting usessortKey/sortDirectionquery params and requires@Sortableon the field (400 otherwise). List responses only include@Listablefields unlessnoPluckis set.- Filters come from
filter_<name>=<mode>:<value>query params (plain value meanseq); modes areeq|ne|gt|ge|lt|le(the default filter compares the raw query-string value — pass a custom filter function to cast for numeric ranges). Only fields declared with@Filter()are filterable. @Joinedand@Computedfields are materialized in-database, so@Sortableand@Filter()work efficiently on them — but those decorators must still be applied explicitly. Such fields are forced read-only and to non-indexed sorting.- Route selection: pass a subset like
{ get: DefaultRoutes.Get, list: DefaultRoutes.List }instead ofDefaultRoutes.All; useDefaultRoutes.WithOptions(route, options, endpoint)to rename an endpoint or preset parameter options. @Accessaccepts per-action overrides, e.g.@Access(AccessMode.ReadOnly, { edit: AccessMode.ReadWrite }).editis a partial update: a field absent from the body is left unchanged,nullclears it, and setters only run for the fields present.@Mandatory("edit")rejects a field cleared by the body (null,"",[]), not an absent one.newstill writes every writable field and runs every setter.- A
@Validatormay return a parse result{ success, data }(e.g. zod'ssafeParseAsync(value)) instead of a boolean:datais then written in place of the raw value.
Deeper reference
See this package's docs/ chapters — Introduction, Data Controllers, Routes, Access Rights,
Validators, Listable Fields, Foreign Keys, Filters, Modifiers, Joined Fields, Computed Fields — and
the shipped .d.ts files for exact signatures. Do not duplicate them here.
