From d51a9c6468a002e131e74225a7e9c45061a26200 Mon Sep 17 00:00:00 2001 From: Antony Rizzitelli Date: Sun, 19 Jul 2026 21:56:06 +0200 Subject: [PATCH 1/4] feat(skills): ship a consumer skill with the package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add skills/-interface/SKILL.md — a lean, source-grounded guide for agents consuming this interface (imports, minimal example, gotchas) — declared via antelopeJs.skills and published through the files array. Consumers receive it automatically: the antelopejs Claude Code plugin syncs package-shipped skills into a project's .claude/skills/, and the cms-ai chatbox loads them at runtime. Content was fact-checked against src/ and docs/ by an adversarial review pass (imports validated against the exports map, examples verified against real signatures). --- package.json | 8 ++- skills/data-api-interface/SKILL.md | 89 ++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 skills/data-api-interface/SKILL.md diff --git a/package.json b/package.json index f8778d1..15b3b35 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ - "dist" + "dist", + "skills" ], "exports": { ".": { @@ -79,6 +80,9 @@ }, "antelopeJs": { "standalone": true, - "test": "src/antelope.test.ts" + "test": "src/antelope.test.ts", + "skills": [ + "./skills" + ] } } diff --git a/skills/data-api-interface/SKILL.md b/skills/data-api-interface/SKILL.md new file mode 100644 index 0000000..a94d7f5 --- /dev/null +++ b/skills/data-api-interface/SKILL.md @@ -0,0 +1,89 @@ +--- +name: data-api-interface +description: AntelopeJS interface that generates CRUD REST endpoints from a database table via a DataController class and field decorators (Access, Listable, Mandatory, Sortable, Filter, Foreign, Joined, Computed, Validator). Use when importing @antelopejs/interface-data-api or @antelopejs/interface-data-api/metadata, when building a data controller / data API over an interface-database table, or when working with DataController, DefaultRoutes, RegisterDataController, ModelReference, or filter_/sortKey/sortDirection list query parameters. +category: antelopejs-interface +tags: [antelopejs, data-api, crud, rest, decorators] +--- + +# 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 + +```typescript +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 + +```typescript +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("users", "default") +class User extends Table { + declare _id: string; + + @Field("string") + declare email: string; +} + +class UserModel extends BasicDataModel(User, "users") {} + +@RegisterDataController() +class UserAPI extends DataController(User, DefaultRoutes.All, Controller("/users")) { + @ModelReference() + @Model(UserModel, "my-database") + declare userModel: UserModel; + + @Listable() + @Access(AccessMode.ReadOnly) + declare _id: string; + + @Listable() + @Sortable() + @Mandatory("new") + @Access(AccessMode.ReadWrite) + declare email: string; +} +``` + +This exposes `GET /users/get?id=`, `GET /users/list`, `POST /users/new`, `PUT /users/edit?id=`, +`DELETE /users/delete?id=` (repeat `id` to delete several). + +## Gotchas + +- `@RegisterDataController()` is mandatory: it wires field decorators, parameter providers, and + routes. A `@ModelReference()` property holding a `@Model` instance is required too (`Query.GetModel` + throws 500 without it). +- 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). +- `list` responds `{ results, total, offset, limit }`. `limit` defaults to 10 and is capped at 100 + (`maxPage`). Sorting uses `sortKey`/`sortDirection` query params and requires `@Sortable` on the + field (400 otherwise). List responses only include `@Listable` fields unless `noPluck` is set. +- Filters come from `filter_=:` query params (plain value means `eq`); modes are + `eq|ne|gt|ge|lt|le`. Only fields declared with `@Filter()` are filterable. +- `@Joined` and `@Computed` fields are materialized in-database (sortable/filterable natively) but + are forced read-only and to non-indexed sorting. +- Route selection: pass a subset like `{ get: DefaultRoutes.Get, list: DefaultRoutes.List }` instead + of `DefaultRoutes.All`; use `DefaultRoutes.WithOptions(route, options, endpoint)` to rename an + endpoint or preset parameter options. +- `@Access` accepts per-action overrides, e.g. `@Access(AccessMode.ReadOnly, { edit: AccessMode.ReadWrite })`. + +## 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. From 9c6ae90cd072b5b36458dd1f629a108eb2b5480c Mon Sep 17 00:00:00 2001 From: Antony Rizzitelli Date: Sun, 19 Jul 2026 22:17:15 +0200 Subject: [PATCH 2/4] address greptile review feedback (greploop iteration 1) --- skills/data-api-interface/SKILL.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/skills/data-api-interface/SKILL.md b/skills/data-api-interface/SKILL.md index a94d7f5..d0f82f1 100644 --- a/skills/data-api-interface/SKILL.md +++ b/skills/data-api-interface/SKILL.md @@ -74,9 +74,11 @@ This exposes `GET /users/get?id=`, `GET /users/list`, `POST /users/new`, `PUT /u (`maxPage`). Sorting uses `sortKey`/`sortDirection` query params and requires `@Sortable` on the field (400 otherwise). List responses only include `@Listable` fields unless `noPluck` is set. - Filters come from `filter_=:` query params (plain value means `eq`); modes are - `eq|ne|gt|ge|lt|le`. Only fields declared with `@Filter()` are filterable. -- `@Joined` and `@Computed` fields are materialized in-database (sortable/filterable natively) but - are forced read-only and to non-indexed sorting. + `eq|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. +- `@Joined` and `@Computed` fields are materialized in-database, so `@Sortable` and `@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 of `DefaultRoutes.All`; use `DefaultRoutes.WithOptions(route, options, endpoint)` to rename an endpoint or preset parameter options. From 7f18f9c0510710e9eb0f62acfe306a9236e1455f Mon Sep 17 00:00:00 2001 From: Antony Rizzitelli Date: Sun, 19 Jul 2026 23:32:48 +0200 Subject: [PATCH 3/4] fix(skills): ship docs with the package --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 15b3b35..c7e5ffb 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "types": "dist/index.d.ts", "files": [ "dist", + "docs", "skills" ], "exports": { From c4dea653dc70c7544f84b378d3106e8c5727b31d Mon Sep 17 00:00:00 2001 From: Antony Rizzitelli Date: Mon, 20 Jul 2026 16:13:18 +0200 Subject: [PATCH 4/4] docs(skills): use fictional domains in code examples --- skills/data-api-interface/SKILL.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/skills/data-api-interface/SKILL.md b/skills/data-api-interface/SKILL.md index d0f82f1..42ea05d 100644 --- a/skills/data-api-interface/SKILL.md +++ b/skills/data-api-interface/SKILL.md @@ -32,21 +32,21 @@ import { DataController, DefaultRoutes, RegisterDataController } from "@antelope 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("users", "default") -class User extends Table { +@RegisterTable("tasks", "default") +class Task extends Table { declare _id: string; @Field("string") - declare email: string; + declare summary: string; } -class UserModel extends BasicDataModel(User, "users") {} +class TaskModel extends BasicDataModel(Task, "tasks") {} @RegisterDataController() -class UserAPI extends DataController(User, DefaultRoutes.All, Controller("/users")) { +class TaskAPI extends DataController(Task, DefaultRoutes.All, Controller("/tasks")) { @ModelReference() - @Model(UserModel, "my-database") - declare userModel: UserModel; + @Model(TaskModel, "my-database") + declare taskModel: TaskModel; @Listable() @Access(AccessMode.ReadOnly) @@ -56,12 +56,12 @@ class UserAPI extends DataController(User, DefaultRoutes.All, Controller("/users @Sortable() @Mandatory("new") @Access(AccessMode.ReadWrite) - declare email: string; + declare summary: string; } ``` -This exposes `GET /users/get?id=`, `GET /users/list`, `POST /users/new`, `PUT /users/edit?id=`, -`DELETE /users/delete?id=` (repeat `id` to delete several). +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