@antelopejs/interface-data-api 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +26 -22
  2. package/dist/antelope.test.js +1 -5
  3. package/dist/antelope.test.js.map +1 -1
  4. package/dist/components.d.ts +67 -39
  5. package/dist/components.js +106 -685
  6. package/dist/components.js.map +1 -1
  7. package/dist/index.d.ts +57 -68
  8. package/dist/index.js +114 -100
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal/computed.d.ts +6 -0
  11. package/dist/internal/computed.js +51 -0
  12. package/dist/internal/computed.js.map +1 -0
  13. package/dist/internal/controller-types.d.ts +34 -0
  14. package/dist/internal/controller-types.js +4 -0
  15. package/dist/internal/controller-types.js.map +1 -0
  16. package/dist/internal/default-filter.d.ts +4 -0
  17. package/dist/internal/default-filter.js +29 -0
  18. package/dist/internal/default-filter.js.map +1 -0
  19. package/dist/internal/deferred-joined-stream.d.ts +11 -0
  20. package/dist/internal/deferred-joined-stream.js +125 -0
  21. package/dist/internal/deferred-joined-stream.js.map +1 -0
  22. package/dist/internal/extractors.d.ts +25 -0
  23. package/dist/internal/extractors.js +31 -0
  24. package/dist/internal/extractors.js.map +1 -0
  25. package/dist/internal/field-types.d.ts +199 -0
  26. package/dist/internal/field-types.js +4 -0
  27. package/dist/internal/field-types.js.map +1 -0
  28. package/dist/internal/filters.d.ts +6 -0
  29. package/dist/internal/filters.js +22 -0
  30. package/dist/internal/filters.js.map +1 -0
  31. package/dist/internal/foreign.d.ts +6 -0
  32. package/dist/internal/foreign.js +116 -0
  33. package/dist/internal/foreign.js.map +1 -0
  34. package/dist/internal/guards.d.ts +39 -0
  35. package/dist/internal/guards.js +116 -0
  36. package/dist/internal/guards.js.map +1 -0
  37. package/dist/internal/joined.d.ts +28 -0
  38. package/dist/internal/joined.js +127 -0
  39. package/dist/internal/joined.js.map +1 -0
  40. package/dist/internal/list.d.ts +16 -0
  41. package/dist/internal/list.js +182 -0
  42. package/dist/internal/list.js.map +1 -0
  43. package/dist/internal/meta-records.d.ts +19 -0
  44. package/dist/internal/meta-records.js +78 -0
  45. package/dist/internal/meta-records.js.map +1 -0
  46. package/dist/internal/properties.d.ts +4 -0
  47. package/dist/internal/properties.js +45 -0
  48. package/dist/internal/properties.js.map +1 -0
  49. package/dist/internal/prototypes.d.ts +14 -0
  50. package/dist/internal/prototypes.js +38 -0
  51. package/dist/internal/prototypes.js.map +1 -0
  52. package/dist/internal/read-properties.d.ts +3 -0
  53. package/dist/internal/read-properties.js +29 -0
  54. package/dist/internal/read-properties.js.map +1 -0
  55. package/dist/internal/table-reference.d.ts +11 -0
  56. package/dist/internal/table-reference.js +46 -0
  57. package/dist/internal/table-reference.js.map +1 -0
  58. package/dist/internal/tables.d.ts +28 -0
  59. package/dist/internal/tables.js +48 -0
  60. package/dist/internal/tables.js.map +1 -0
  61. package/dist/internal/validation.d.ts +12 -0
  62. package/dist/internal/validation.js +100 -0
  63. package/dist/internal/validation.js.map +1 -0
  64. package/dist/metadata.d.ts +27 -228
  65. package/dist/metadata.js +84 -213
  66. package/dist/metadata.js.map +1 -1
  67. package/dist/tests/components/access_control.test.js +28 -20
  68. package/dist/tests/components/access_control.test.js.map +1 -1
  69. package/dist/tests/components/computed.test.js +36 -27
  70. package/dist/tests/components/computed.test.js.map +1 -1
  71. package/dist/tests/components/cross_instance.test.js +8 -6
  72. package/dist/tests/components/cross_instance.test.js.map +1 -1
  73. package/dist/tests/components/deferred_joined.test.js +57 -54
  74. package/dist/tests/components/deferred_joined.test.js.map +1 -1
  75. package/dist/tests/components/extract_generic.test.d.ts +1 -0
  76. package/dist/tests/components/extract_generic.test.js +64 -0
  77. package/dist/tests/components/extract_generic.test.js.map +1 -0
  78. package/dist/tests/components/filter_comparisons.test.d.ts +1 -0
  79. package/dist/tests/components/filter_comparisons.test.js +34 -0
  80. package/dist/tests/components/filter_comparisons.test.js.map +1 -0
  81. package/dist/tests/components/filter_list_route.test.d.ts +1 -0
  82. package/dist/tests/components/filter_list_route.test.js +182 -0
  83. package/dist/tests/components/filter_list_route.test.js.map +1 -0
  84. package/dist/tests/components/filter_options.test.js +11 -10
  85. package/dist/tests/components/filter_options.test.js.map +1 -1
  86. package/dist/tests/components/foreign_computed.test.js +23 -14
  87. package/dist/tests/components/foreign_computed.test.js.map +1 -1
  88. package/dist/tests/components/foreign_joined.test.js +31 -27
  89. package/dist/tests/components/foreign_joined.test.js.map +1 -1
  90. package/dist/tests/components/joined.test.js +59 -45
  91. package/dist/tests/components/joined.test.js.map +1 -1
  92. package/dist/tests/components/listable.test.js +78 -58
  93. package/dist/tests/components/listable.test.js.map +1 -1
  94. package/dist/tests/components/localized_sort.test.js +14 -11
  95. package/dist/tests/components/localized_sort.test.js.map +1 -1
  96. package/dist/tests/components/mandatory.test.js +25 -25
  97. package/dist/tests/components/mandatory.test.js.map +1 -1
  98. package/dist/tests/components/modifier_key.test.js +27 -18
  99. package/dist/tests/components/modifier_key.test.js.map +1 -1
  100. package/dist/tests/components/parse_body.test.d.ts +1 -0
  101. package/dist/tests/components/parse_body.test.js +76 -0
  102. package/dist/tests/components/parse_body.test.js.map +1 -0
  103. package/dist/tests/components/parsed_value.test.js +23 -21
  104. package/dist/tests/components/parsed_value.test.js.map +1 -1
  105. package/dist/tests/components/partial_edit.test.js +38 -32
  106. package/dist/tests/components/partial_edit.test.js.map +1 -1
  107. package/dist/tests/components/per_action_access.test.js +20 -12
  108. package/dist/tests/components/per_action_access.test.js.map +1 -1
  109. package/dist/tests/components/prototype_named_filter.test.d.ts +1 -0
  110. package/dist/tests/components/prototype_named_filter.test.js +87 -0
  111. package/dist/tests/components/prototype_named_filter.test.js.map +1 -0
  112. package/dist/tests/components/validator.test.js +45 -37
  113. package/dist/tests/components/validator.test.js.map +1 -1
  114. package/dist/tests/components/written_row.test.d.ts +1 -0
  115. package/dist/tests/components/written_row.test.js +99 -0
  116. package/dist/tests/components/written_row.test.js.map +1 -0
  117. package/dist/tests/helpers/other-instance.d.ts +11 -1
  118. package/dist/tests/helpers/other-instance.js +35 -9
  119. package/dist/tests/helpers/other-instance.js.map +1 -1
  120. package/dist/tests/index/field_decorator_target.test.d.ts +1 -0
  121. package/dist/tests/index/field_decorator_target.test.js +20 -0
  122. package/dist/tests/index/field_decorator_target.test.js.map +1 -0
  123. package/dist/tests/index/get_guard.test.js +6 -6
  124. package/dist/tests/index/get_guard.test.js.map +1 -1
  125. package/dist/tests/index/inheritance.test.js +14 -9
  126. package/dist/tests/index/inheritance.test.js.map +1 -1
  127. package/dist/tests/index/metadata_names.test.d.ts +1 -0
  128. package/dist/tests/index/metadata_names.test.js +91 -0
  129. package/dist/tests/index/metadata_names.test.js.map +1 -0
  130. package/dist/tests/index/routes.test.js +49 -44
  131. package/dist/tests/index/routes.test.js.map +1 -1
  132. package/dist/tests/integration.test.d.ts +1 -1
  133. package/dist/tests/integration.test.js +66 -729
  134. package/dist/tests/integration.test.js.map +1 -1
  135. package/dist/tests/internal/controller-fields.test.d.ts +1 -0
  136. package/dist/tests/internal/controller-fields.test.js +25 -0
  137. package/dist/tests/internal/controller-fields.test.js.map +1 -0
  138. package/dist/tests/internal/http.d.ts +35 -0
  139. package/dist/tests/internal/http.js +56 -0
  140. package/dist/tests/internal/http.js.map +1 -0
  141. package/dist/tests/internal/shop-api.d.ts +1 -0
  142. package/dist/tests/internal/shop-api.js +331 -0
  143. package/dist/tests/internal/shop-api.js.map +1 -0
  144. package/dist/tests/internal/shop-schema.d.ts +162 -0
  145. package/dist/tests/internal/shop-schema.js +387 -0
  146. package/dist/tests/internal/shop-schema.js.map +1 -0
  147. package/dist/tests/utils.d.ts +8 -10
  148. package/dist/tests/utils.js +1 -3
  149. package/dist/tests/utils.js.map +1 -1
  150. package/dist/tests/utils.test.js +10 -11
  151. package/dist/tests/utils.test.js.map +1 -1
  152. package/docs/1.introduction.md +38 -35
  153. package/docs/10.joined.md +51 -40
  154. package/docs/11.computed.md +91 -47
  155. package/docs/2.data-controllers.md +50 -42
  156. package/docs/3.routes.md +156 -134
  157. package/docs/4.access-rights.md +48 -44
  158. package/docs/5.validators.md +65 -47
  159. package/docs/6.listable.md +53 -49
  160. package/docs/7.foreign-keys.md +55 -48
  161. package/docs/8.filters.md +65 -54
  162. package/docs/9.modifiers.md +29 -25
  163. package/package.json +34 -13
  164. package/skills/data-api-interface/SKILL.md +13 -3
  165. package/skills/data-api-interface/cases.json +99 -0
@@ -1,14 +1,18 @@
1
- # Data Controllers
1
+ ---
2
+ title: Data controllers
3
+ description: The DataController function, the RegisterDataController decorator, and the parts every data controller declares.
4
+ mode: reference
5
+ ---
2
6
 
3
7
  ## Overview
4
8
 
5
- A data controller is the central building block of the Data API. It connects a database table to a set of API endpoints, exposing the table fields you declare on the class. The `DataController` function creates a base class that combines your table definition, route definitions, and controller path into a single cohesive unit.
9
+ A data controller connects a database table to a set of API endpoints and exposes the table fields you declare on the class. The `DataController` function returns a base class built from three parts: the table class, the route definitions and the controller base that holds the endpoint path.
6
10
 
7
- ## Create a Data Controller
11
+ ## Create a data controller
8
12
 
9
- Use the `DataController` function to define a new controller. It accepts three arguments: the table class, a route definition object, and a controller base with the endpoint path.
13
+ `DataController` takes three arguments: the table class, a route definition object, and a controller base with the endpoint path.
10
14
 
11
- This example uses the `myapp` schema and the `my-database` instance. Import the table declaration and await `RegisterSchema("myapp")` in the declaring module before serving requests; provision the named instance through your database setup.
15
+ The example uses the `myapp` schema and the `my-database` instance. Import the table declaration and await `RegisterSchema("myapp")` in the declaring module before it serves requests. Your database setup provisions the named instance.
12
16
 
13
17
  ```ts docs-check
14
18
  import { Controller } from "@antelopejs/interface-api";
@@ -31,7 +35,6 @@ import {
31
35
  Table,
32
36
  } from "@antelopejs/interface-database-decorators";
33
37
 
34
- // Define the database table
35
38
  @RegisterTable("users", "myapp")
36
39
  class User extends Table {
37
40
  @Field("string")
@@ -44,10 +47,8 @@ class User extends Table {
44
47
  declare age: number;
45
48
  }
46
49
 
47
- // Create the database model
48
50
  class UserModel extends BasicDataModel(User, "users") {}
49
51
 
50
- // Create the data controller
51
52
  @RegisterDataController()
52
53
  class UserAPI extends DataController(
53
54
  User,
@@ -71,50 +72,57 @@ class UserAPI extends DataController(
71
72
  }
72
73
  ```
73
74
 
74
- ## `DataController` Signature
75
-
76
- ```typescript
77
- DataController(
78
- tableClass: typeof Table, // Database table class
79
- routes: DataControllerDef, // Route definitions (e.g., DefaultRoutes.All)
80
- base: typeof Controller, // Controller base with path
81
- )
75
+ ## `DataController` signature
76
+
77
+ ```typescript fragment
78
+ function DataController<
79
+ C extends Class,
80
+ P extends DataControllerDef = DataControllerDef,
81
+ Base extends ControllerClass = ControllerClass,
82
+ >(
83
+ tableClass: C,
84
+ def: P,
85
+ base: Base,
86
+ ): Class<ExtractDefCallbacks<P> & { table: InstanceType<C> }> & Base;
82
87
  ```
83
88
 
84
- | Parameter | Description |
85
- | ------------ | --------------------------------------------------------------------------------------------------------------------------------- |
86
- | `tableClass` | The database table class decorated with `@RegisterTable`. The controller validates that this table exists in the database schema. |
87
- | `routes` | An object mapping endpoint names to route callbacks. Use `DefaultRoutes.All` for standard CRUD or pass a custom subset. |
88
- | `base` | The controller base, typically created with `Controller("/path")`, which sets the URL prefix for all endpoints. |
89
+ | Parameter | Description |
90
+ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
91
+ | `tableClass` | The table class decorated with `@RegisterTable`. `DataController` throws `Non-existent Database Schema` when the table's schema is not registered, and `Unregistered Database Table` when the schema does not hold the table. |
92
+ | `def` | An object that maps endpoint names to route callbacks. Use `DefaultRoutes.All` for the five CRUD routes, or pass a subset or custom routes. |
93
+ | `base` | The controller base, usually `Controller("/path")` from `@antelopejs/interface-api`. Its location is the URL prefix of every endpoint. |
94
+
95
+ The returned class extends `base`. Its prototype holds one method per key of `def`, and its instances declare a `table` field typed by the table class.
89
96
 
90
- ## Required Components
97
+ ## Required components
91
98
 
92
- Every data controller requires the following elements:
99
+ Every data controller declares these four parts.
93
100
 
94
- 1. **Table Class** - A database table decorated with `@RegisterTable`, defining the schema.
95
- 2. **Route Definitions** - Which operations to expose. Use `DefaultRoutes.All` or a custom selection.
96
- 3. **Controller Base** - The URL path prefix, created with `Controller("/path")`.
97
- 4. **Model Reference** - A property decorated with `@ModelReference()` that holds a database model instance. This model provides the actual database operations (insert, get, update, delete). A controller derived from another data controller (for example `Controller("/path", ParentController)`) inherits its model reference unless it declares its own.
101
+ 1. **Table class.** A database table decorated with `@RegisterTable`, which defines the schema.
102
+ 2. **Route definitions.** The operations to expose. Use `DefaultRoutes.All` or a custom selection.
103
+ 3. **Controller base.** The URL path prefix, created with `Controller("/path")`.
104
+ 4. **Model reference.** A property decorated with `@ModelReference()` that holds a database model instance. The model runs the database operations (insert, get, update and delete). A controller derived from another data controller (for example `Controller("/path", ParentController)`) inherits its model reference unless it declares its own.
98
105
 
99
- ## The `@RegisterDataController` Decorator
106
+ ## The `@RegisterDataController` decorator
100
107
 
101
- Apply `@RegisterDataController()` to the class to finalize the controller setup. This decorator processes all field decorators, registers the defined routes, and connects parameter providers to each endpoint.
108
+ Apply `@RegisterDataController()` to the class to finish the controller setup. For each route of the definition, the decorator applies the route's parameter decorators, registers a prefix route that records the route entry on the request context, and registers the route method as the handler at `<controller location>/<endpoint>`.
102
109
 
103
- ## Field Declarations
110
+ ## Field declarations
104
111
 
105
- Fields declared on the data controller class determine which database columns are exposed through the API. Each field can be annotated with decorators to control its behavior:
112
+ The fields you declare on the data controller class decide which table fields the API exposes. Each field takes decorators that set its behaviour:
106
113
 
107
- - `@Access` - Set read/write permissions.
108
- - `@Listable` - Include the field in list responses.
109
- - `@Mandatory` - Require the field on specific operations.
110
- - `@Validator` - Attach a validation function.
111
- - `@Foreign` - Establish a foreign key relationship.
112
- - `@Joined` - Flatten a scalar field from another table onto the row.
113
- - `@Filter` - Enable filtering on the field.
114
- - `@Sortable` - Allow sorting by the field.
114
+ - `@Access` sets read and write permissions.
115
+ - `@Listable` includes the field in list responses.
116
+ - `@Mandatory` requires the field on specific operations.
117
+ - `@Validator` attaches a validation function.
118
+ - `@Foreign` declares a foreign key relationship.
119
+ - `@Joined` flattens a scalar field from another table onto the row.
120
+ - `@Computed` adds a value the database computes for each row.
121
+ - `@Filter` enables filtering on the field.
122
+ - `@Sortable` allows sorting by the field.
115
123
 
116
- Fields without any decorator are ignored by the Data API and do not appear in API responses or accept input.
124
+ The Data API ignores a field without an access mode. Such a field never appears in responses and never accepts input. `@Access` gives a field its mode, and `@Joined` and `@Computed` make their field read-only.
117
125
 
118
- ## Next Steps
126
+ ## Next steps
119
127
 
120
- See the [routes documentation](./3.routes.md) to learn about available routes and how to customize them.
128
+ The [routes page](./3.routes.md) lists the available routes and how to customize them.
package/docs/3.routes.md CHANGED
@@ -1,18 +1,22 @@
1
- # Routes
1
+ ---
2
+ title: Routes
3
+ description: The default CRUD routes, their parameters and responses, the WithOptions and WithGetGuard helpers, custom routes, and route errors.
4
+ mode: reference
5
+ ---
2
6
 
3
7
  ## Overview
4
8
 
5
- Routes define which operations are available on a data controller. The module provides predefined route configurations that automatically generate endpoints with the correct HTTP methods, parameter extraction, and response handling.
9
+ Routes define which operations a data controller serves. The `DefaultRoutes` namespace holds ready-made routes. Each one sets its HTTP method, the parameters it reads from the request and the response it sends.
6
10
 
7
- ## Default Routes
11
+ ## Default routes
8
12
 
9
- The `DefaultRoutes` namespace provides five built-in routes covering standard CRUD operations.
13
+ The `DefaultRoutes` namespace holds five routes for the CRUD operations.
10
14
 
11
15
  ```typescript
12
16
  import { DefaultRoutes } from "@antelopejs/interface-data-api";
13
17
  ```
14
18
 
15
- | Route | Object | HTTP Method | Endpoint Pattern |
19
+ | Route | Object | HTTP method | Endpoint pattern |
16
20
  | ------ | ---------------------- | ----------- | -------------------------- |
17
21
  | Get | `DefaultRoutes.Get` | GET | `/resource/get?id=<id>` |
18
22
  | List | `DefaultRoutes.List` | GET | `/resource/list` |
@@ -20,13 +24,13 @@ import { DefaultRoutes } from "@antelopejs/interface-data-api";
20
24
  | Edit | `DefaultRoutes.Edit` | PUT | `/resource/edit?id=<id>` |
21
25
  | Delete | `DefaultRoutes.Delete` | DELETE | `/resource/delete?id=<id>` |
22
26
 
23
- Use `DefaultRoutes.All` to include all five routes at once.
27
+ `DefaultRoutes.All` holds all five routes under the keys `get`, `list`, `new`, `edit` and `delete`.
24
28
 
25
- ## Route Configuration
29
+ ## Route configuration
26
30
 
27
- Pass a route definition object to `DataController` to control which endpoints are created.
31
+ The route definition object you pass to `DataController` decides which endpoints exist. Each key is an endpoint name.
28
32
 
29
- ```typescript
33
+ ```typescript fragment
30
34
  // All CRUD routes
31
35
  @RegisterDataController()
32
36
  class UserAPI extends DataController(
@@ -48,31 +52,27 @@ class UserAPI extends DataController(
48
52
  },
49
53
  Controller("/users"),
50
54
  ) {
51
- // edit and delete endpoints are not created
55
+ // no edit or delete endpoint
52
56
  }
53
57
  ```
54
58
 
55
- ## Route Details
59
+ ## Route details
56
60
 
57
61
  ### Get
58
62
 
59
- Retrieves a single record by its identifier.
60
-
61
- **Endpoint:** `GET /resource/get`
62
-
63
- **Query Parameters:**
63
+ Reads one record by its identifier. The endpoint is `GET /resource/get`.
64
64
 
65
- | Parameter | Required | Description |
66
- | --------- | -------- | ----------------- |
67
- | `id` | Yes | Record identifier |
65
+ | Query parameter | Required | Description |
66
+ | --------------- | -------- | ----------------- |
67
+ | `id` | Yes | Record identifier |
68
68
 
69
- **Example:**
69
+ A request without `id` answers `400` with `Missing id.`. A missing record answers `404 Not Found`.
70
70
 
71
71
  ```http
72
72
  GET /users/get?id=user-123
73
73
  ```
74
74
 
75
- **Response:**
75
+ The response holds the readable fields of the record.
76
76
 
77
77
  ```json
78
78
  {
@@ -84,26 +84,21 @@ GET /users/get?id=user-123
84
84
 
85
85
  ### List
86
86
 
87
- Retrieves multiple records with pagination, sorting, and filtering support.
88
-
89
- **Endpoint:** `GET /resource/list`
90
-
91
- **Query Parameters:**
87
+ Reads one page of records, with sorting and filtering. The endpoint is `GET /resource/list`.
92
88
 
93
- | Parameter | Required | Default | Description |
94
- | --------------- | -------- | ------- | --------------------------------------------- |
95
- | `limit` | No | `10` | Maximum number of records to return |
96
- | `offset` | No | `0` | Number of records to skip |
97
- | `sortKey` | No | - | Field to sort by (must be marked `@Sortable`) |
98
- | `sortDirection` | No | `asc` | Sort direction: `asc` or `desc` |
99
-
100
- **Example:**
89
+ | Query parameter | Required | Default | Description |
90
+ | ---------------- | -------- | -------------------- | ------------------------------------------------------------------------------------------------ |
91
+ | `limit` | No | `maxPage`, else `10` | Maximum number of records to return. The route caps it at `maxPage`, or at `100` without one. |
92
+ | `offset` | No | `0` | Number of records to skip |
93
+ | `sortKey` | No | none | Field to sort by. The field needs `@Sortable`, or the route answers `400 Field is not sortable.` |
94
+ | `sortDirection` | No | `asc` | `asc` or `desc`. Any other value sorts ascending. |
95
+ | `filter_<field>` | No | none | A filter on a field declared with `@Filter`. See [filters](./8.filters.md). |
101
96
 
102
97
  ```http
103
98
  GET /users/list?limit=10&offset=0&sortKey=name&sortDirection=asc
104
99
  ```
105
100
 
106
- **Response:**
101
+ The response holds the page, the total count of matching records, and the `offset` and `limit` the route used.
107
102
 
108
103
  ```json
109
104
  {
@@ -119,13 +114,7 @@ GET /users/list?limit=10&offset=0&sortKey=name&sortDirection=asc
119
114
 
120
115
  ### New
121
116
 
122
- Creates a new record. The request body must be a JSON object with the fields to set.
123
-
124
- **Endpoint:** `POST /resource/new`
125
-
126
- **Body:** JSON object with field values.
127
-
128
- **Example:**
117
+ Creates a record. The endpoint is `POST /resource/new`, and the body is a JSON object with the field values.
129
118
 
130
119
  ```http
131
120
  POST /users/new
@@ -138,7 +127,7 @@ Content-Type: application/json
138
127
  }
139
128
  ```
140
129
 
141
- **Response:** the created record, as [Get](#get) answers it, with its `_id` even when `get` does not read it.
130
+ The response is the created record, as [Get](#get) answers it, with its `_id` even when `get` does not read `_id`.
142
131
 
143
132
  ```json
144
133
  {
@@ -148,23 +137,19 @@ Content-Type: application/json
148
137
  }
149
138
  ```
150
139
 
151
- Every writable field is written: a field absent from the body is written as `undefined`, and every writable setter of the controller runs (with `undefined` for absent fields). A body that is not valid JSON or not a JSON object is rejected with `400 Bad Request`.
140
+ The row starts from the defaults the controller class declares. Every writable field is then written. A field absent from the body is written as `undefined`, and every writable setter of the controller runs, with `undefined` for an absent field. The route answers `400 Bad Request` for a body that is not valid JSON or not a JSON object.
152
141
 
153
- Before inserting, the `insert` event is triggered on any table modifiers attached to the database table.
142
+ Before the insert, the route triggers the `insert` event on the modifiers of the table.
154
143
 
155
144
  ### Edit
156
145
 
157
- Updates an existing record by its identifier.
146
+ Updates a record by its identifier. The endpoint is `PUT /resource/edit`.
158
147
 
159
- **Endpoint:** `PUT /resource/edit`
148
+ | Query parameter | Required | Description |
149
+ | --------------- | -------- | ----------------- |
150
+ | `id` | Yes | Record identifier |
160
151
 
161
- **Query Parameters:**
162
-
163
- | Parameter | Required | Description |
164
- | --------- | -------- | ----------------- |
165
- | `id` | Yes | Record identifier |
166
-
167
- **Body:** JSON object with the fields to update. Only the fields present in the body are written:
152
+ The body is a JSON object with the fields to update. The route writes only the fields present in the body.
168
153
 
169
154
  | In the body | Effect on the field |
170
155
  | ----------- | ------------------- |
@@ -172,11 +157,9 @@ Updates an existing record by its identifier.
172
157
  | `null` | cleared |
173
158
  | a value | set |
174
159
 
175
- Writable setters only run for the fields present in the body. Modifier fields (localized, encrypted…) absent from the body keep their stored value in every language.
160
+ Writable setters run only for the fields present in the body. A modifier field (localized or encrypted, for example) absent from the body keeps its stored value in every language.
176
161
 
177
- A missing record answers `404 Not Found`. A body that is not valid JSON or not a JSON object is rejected with `400 Bad Request`.
178
-
179
- **Example:**
162
+ A request without `id` answers `400` with `Missing id.`. A missing record answers `404 Not Found`. A body that is not valid JSON or not a JSON object answers `400 Bad Request`.
180
163
 
181
164
  ```http
182
165
  PUT /users/edit?id=user-123
@@ -188,7 +171,7 @@ Content-Type: application/json
188
171
  }
189
172
  ```
190
173
 
191
- **Response:** the stored record after the update, as [Get](#get) answers it (joined, computed and foreign fields included), so the caller needs no second read.
174
+ The response is the stored record after the update, as [Get](#get) answers it, with joined, computed and foreign fields. The caller needs no second read.
192
175
 
193
176
  ```json
194
177
  {
@@ -198,49 +181,45 @@ Content-Type: application/json
198
181
  }
199
182
  ```
200
183
 
201
- Before updating, the `update` event is triggered on any table modifiers attached to the database table.
184
+ Before the update, the route triggers the `update` event on the modifiers of the table.
202
185
 
203
186
  ### Delete
204
187
 
205
- Deletes one or more records by their identifiers.
206
-
207
- **Endpoint:** `DELETE /resource/delete`
188
+ Deletes one or more records by their identifiers. The endpoint is `DELETE /resource/delete`.
208
189
 
209
- **Query Parameters:**
190
+ | Query parameter | Required | Description |
191
+ | --------------- | -------- | ----------------------------------------------------------------------------------- |
192
+ | `id` | Yes | Record identifier. Repeat the `id` parameter to delete several records in one call. |
210
193
 
211
- | Parameter | Required | Description |
212
- | --------- | -------- | -------------------------------------------------------------------------------------- |
213
- | `id` | Yes | Record identifier(s). Pass multiple `id` parameters to delete several records at once. |
214
-
215
- **Example:**
194
+ A request without `id` answers `400` with `Missing id.`.
216
195
 
217
196
  ```http
218
197
  DELETE /users/delete?id=user-123
219
198
  ```
220
199
 
221
- **Response:**
200
+ The route answers `200 OK`. The body is the number of records the database deleted. An identifier that matches no record is not an error.
222
201
 
223
202
  ```http
224
203
  200 OK
225
204
  ```
226
205
 
227
- ## Route Options with `WithOptions`
206
+ ## Route options with `WithOptions`
228
207
 
229
- The `DefaultRoutes.WithOptions` function customizes route behavior by overriding query parameters or route settings.
208
+ `DefaultRoutes.WithOptions` customizes a route. It sets route options, which take precedence over the query parameters of the same name, and it can rename the endpoint.
230
209
 
231
- ```typescript
210
+ ```typescript fragment
232
211
  DefaultRoutes.WithOptions(callback, options?, endpoint?)
233
212
  ```
234
213
 
235
- | Parameter | Description |
236
- | ---------- | ------------------------------------------------------------------------------------ |
237
- | `callback` | The route callback to customize (e.g., `DefaultRoutes.Edit`). |
238
- | `options` | An object with option overrides. These values take precedence over query parameters. |
239
- | `endpoint` | An optional custom endpoint path. Defaults to the route key name. |
214
+ | Parameter | Description |
215
+ | ---------- | -------------------------------------------------------------------------------------------------------------------------------- |
216
+ | `callback` | The route to customize, such as `DefaultRoutes.Edit`, or a route `WithOptions` already returned. In that case the options merge. |
217
+ | `options` | An object of option values. A query parameter with the same name is ignored. Route `filters` combine with the request's filters. |
218
+ | `endpoint` | A custom endpoint path. It defaults to the route key in the definition object. |
240
219
 
241
220
  ### Example
242
221
 
243
- ```typescript
222
+ ```typescript fragment
244
223
  const routes = {
245
224
  edit: DefaultRoutes.Edit,
246
225
  quickEdit: DefaultRoutes.WithOptions(DefaultRoutes.Edit, {
@@ -250,49 +229,58 @@ const routes = {
250
229
 
251
230
  @RegisterDataController()
252
231
  class UserAPI extends DataController(User, routes, Controller("/users")) {
253
- // /users/edit enforces mandatory fields
254
- // /users/quickEdit skips mandatory field validation
232
+ // /users/edit checks mandatory fields
233
+ // /users/quickEdit skips the mandatory field check
255
234
  }
256
235
  ```
257
236
 
258
- ### Available Options per Route
237
+ ### Available options per route
238
+
239
+ The string options `noForeign` and `noMandatory` take effect for any non-empty string, `"false"` included.
259
240
 
260
- **Get:**
241
+ The Get route takes these options.
261
242
 
262
- | Option | Type | Description |
263
- | ----------- | -------- | ------------------------------------------------ |
264
- | `index` | `string` | Use a secondary index instead of the primary key |
265
- | `noForeign` | `string` | Skip foreign key resolution |
243
+ | Option | Type | Description |
244
+ | ----------- | -------- | -------------------------------------------------------------------- |
245
+ | `index` | `string` | Read the record through a secondary index instead of the primary key |
246
+ | `noForeign` | `string` | Skip foreign key resolution |
266
247
 
267
- **List:**
248
+ The List route takes these options.
268
249
 
269
250
  | Option | Type | Description |
270
251
  | ----------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
271
- | `maxPage` | `number` | Maximum allowed value for `limit` (caps the page size) |
252
+ | `maxPage` | `number` | Maximum value of `limit`, and the page size when the request sends no `limit`. Without it, the cap is `100`. |
272
253
  | `noForeign` | `boolean` | Skip foreign key resolution |
273
- | `noPluck` | `boolean` | Return all database fields instead of plucked fields |
274
- | `pluckMode` | `string` | Select a named pluck mode (see `@Listable`) |
275
- | `filters` | `Record<string, FieldFilter>` | Mandatory filters merged with request filters; route field keys win (see [filters](./8.filters.md#mandatory-route-filters)). |
254
+ | `noPluck` | `boolean` | Skip the database pluck. The response still holds only the fields readable in `list` |
255
+ | `pluckMode` | `string` | Select a named pluck mode (see `@Listable`). A mode that no field declares answers `400`. |
256
+ | `filters` | `Record<string, FieldFilter>` | Mandatory filters merged with request filters. Route field keys win (see [filters](./8.filters.md#mandatory-route-filters)). |
276
257
 
277
- **New:**
258
+ The New route takes this option.
278
259
 
279
- | Option | Type | Description |
280
- | ------------- | -------- | ------------------------------- |
281
- | `noMandatory` | `string` | Skip mandatory field validation |
260
+ | Option | Type | Description |
261
+ | ------------- | -------- | ------------------------------ |
262
+ | `noMandatory` | `string` | Skip the mandatory field check |
282
263
 
283
- **Edit:**
264
+ The Edit route takes these options.
284
265
 
285
- | Option | Type | Description |
286
- | ------------- | -------- | ------------------------------------------------ |
287
- | `index` | `string` | Use a secondary index instead of the primary key |
288
- | `noMandatory` | `string` | Skip mandatory field validation |
266
+ | Option | Type | Description |
267
+ | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
268
+ | `index` | `string` | Find the record and read the response through a secondary index. The update itself targets the record whose primary key equals `id`. |
269
+ | `noMandatory` | `string` | Skip the mandatory field check |
289
270
 
290
- ## Amending the Body of New and Edit
271
+ ## Amending the body of `new` and `edit`
291
272
 
292
- The `new` and `edit` routes take the raw request body (`Buffer`) or a body already parsed (`Validation.WrittenBody`). A route wrapping them can parse the body, amend it (stamp a field, drop one) and hand the object on: the route validates and writes it as handed, without a second serialization.
273
+ The `new` and `edit` routes take the raw request body (`Buffer`) or a body already parsed (`Validation.WrittenBody`). A route that wraps them can parse the body, amend it (stamp a field or drop one) and pass the object on. The route validates and writes the object as it receives it, with no second serialization.
274
+
275
+ A parsed body is `FieldValues`, which holds fields by name. Each field holds a `FieldValue`: a string, a number, a boolean, `null`, a `Date`, or an array or plain object of those. `Validation.ParseBody` answers `400` for anything else, whether it comes from a `Buffer` or from a body already parsed. That includes a function, a class instance, a `bigint`, a cycle and an `undefined` array element.
276
+
277
+ The default routes run with the controller as `this`. A wrapping function declares `this: DataControllerInstance` and calls `route.func.call(this, ...)`.
293
278
 
294
279
  ```ts docs-check
295
- import { DefaultRoutes } from "@antelopejs/interface-data-api";
280
+ import {
281
+ DefaultRoutes,
282
+ type DataControllerInstance,
283
+ } from "@antelopejs/interface-data-api";
296
284
  import {
297
285
  type Parameters,
298
286
  Validation,
@@ -302,7 +290,7 @@ import type { RequestContext } from "@antelopejs/interface-api";
302
290
  const stampedNew = {
303
291
  ...DefaultRoutes.New,
304
292
  func: function (
305
- this: unknown,
293
+ this: DataControllerInstance,
306
294
  ctx: RequestContext,
307
295
  params: Parameters.NewParameters,
308
296
  body: Buffer,
@@ -314,9 +302,9 @@ const stampedNew = {
314
302
  };
315
303
  ```
316
304
 
317
- ## Loaded-Model Guards with `WithGetGuard`
305
+ ## Loaded-model guards with `WithGetGuard`
318
306
 
319
- `DefaultRoutes.WithGetGuard(guard)` returns a Get route that awaits a guard after loading and unlocking the model, but before selecting readable fields and applying response getters. Use it to check a stored discriminator or ownership field that must not appear in the response.
307
+ `DefaultRoutes.WithGetGuard(guard)` returns a Get route that awaits a guard after it loads and unlocks the model. The guard runs before the route selects the readable fields and applies the response getters. A guard can check a stored discriminator or ownership field that never appears in the response.
320
308
 
321
309
  ```ts docs-check
322
310
  import { DefaultRoutes } from "@antelopejs/interface-data-api";
@@ -324,7 +312,7 @@ import { assert } from "@antelopejs/interface-api-util";
324
312
 
325
313
  const invoiceGet = DefaultRoutes.WithGetGuard(
326
314
  async function (ctx, current, params) {
327
- // documentType can be stored without being exposed through @Access.
315
+ // The table stores documentType, and no @Access exposes it.
328
316
  assert(current.documentType === "invoice", 404, "Not Found");
329
317
  },
330
318
  );
@@ -335,28 +323,28 @@ const routes = {
335
323
  };
336
324
  ```
337
325
 
338
- The `GetGuard` callback receives `(ctx, current, params)`. Declare it as a regular function to access the controller through `this`.
326
+ The `GetGuard` callback receives `(ctx, current, params)`. Declare it as a regular function to reach the controller through `this`, typed `DataControllerInstance`. That type is `object`, with no string index, so narrow `this` (with `instanceof` your controller class, for example) before you read a controller field. `current` is `RowFields`, whose values are `unknown`.
339
327
 
340
- | Argument | Contents | Use it for |
341
- | --------- | --------------------------------------------------------------------------------------- | ---------------------------------------------- |
342
- | `ctx` | Request context | Request-specific policy. |
343
- | `current` | Loaded model, including non-readable fields and normal Get joined/computed/foreign data | Inspecting stored ownership or discriminators. |
344
- | `params` | `Parameters.GetParameters` | The parsed Get parameters. |
328
+ | Argument | Contents | Use it for |
329
+ | --------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------- |
330
+ | `ctx` | Request context | Policy that depends on the request |
331
+ | `current` | Loaded model, with the fields no `@Access` reads and the joined, computed and foreign data of Get | Checking stored ownership or discriminators |
332
+ | `params` | `Parameters.GetParameters` | The parsed Get parameters |
345
333
 
346
- Treat `current` as read-only: it is not the response DTO and the API does not freeze it.
334
+ Treat `current` as read-only. It is not the response object, and the API does not freeze it.
347
335
 
348
- Keep these boundaries when writing a guard:
336
+ A guard follows these rules.
349
337
 
350
- - **Deny by throwing.** Throw an `HTTPResult` or use `assert`. A normal return permits the read, including a return value of `false`.
351
- - **Missing records bypass the guard.** The route returns `404` without invoking it.
352
- - **Use the already-loaded model.** The guard does not issue a second read or change field-access rules.
353
- - **Protect each route deliberately.** The guard applies only to the route using it, not list, edit, or delete routes.
338
+ - **Deny by throwing.** Throw an `HTTPResult` or call `assert`. A normal return allows the read, even a return value of `false`.
339
+ - **A missing record skips the guard.** The route answers `404` without calling it.
340
+ - **The guard reads the loaded model.** It issues no second read and does not change field access rules.
341
+ - **Each route needs its own guard.** The guard applies only to the route that uses it, not to the list, edit or delete routes.
354
342
 
355
- ## Custom Routes
343
+ ## Custom routes
356
344
 
357
- For operations beyond CRUD, define a custom route callback with `func`, `args`, and `method` properties.
345
+ For operations beyond CRUD, define a route callback with `func`, `args` and `method` properties.
358
346
 
359
- ```typescript
347
+ ```typescript fragment
360
348
  import { Context } from "@antelopejs/interface-api";
361
349
  import {
362
350
  DataController,
@@ -387,20 +375,54 @@ class UserAPI extends DataController(
387
375
  }
388
376
  ```
389
377
 
390
- The `args` array contains parameter decorators that extract values from the request context. Each decorator corresponds to a parameter in the `func` callback, in order.
378
+ The `args` array holds parameter decorators that read values from the request context. Entry `i` of `args` supplies parameter `i` of `func`.
379
+
380
+ `DataControllerCallback<O, R, A>` states that pairing. `A` is the tuple of what `func` receives, and `args` holds one decorator per element of `A`. The default routes declare their tuple. `DefaultRoutes.Get` takes `[RequestContext, Parameters.GetParameters]`, and `DefaultRoutes.New` and `DefaultRoutes.Edit` add the `Validation.WrittenBody`. A route that wraps one of them can be generic over the tuple and call `route.func.call(this, ...)` with typed arguments. Without `A`, the parameters are unknown and unchecked. A `DataControllerDef` holds routes of that type.
381
+
382
+ ```ts docs-check
383
+ import type { RequestContext } from "@antelopejs/interface-api";
384
+ import {
385
+ type DataControllerCallback,
386
+ type DataControllerInstance,
387
+ DefaultRoutes,
388
+ } from "@antelopejs/interface-data-api";
389
+ import type { Parameters } from "@antelopejs/interface-data-api/components";
390
+
391
+ function withLoggedId<O, R, Rest extends unknown[]>(
392
+ route: DataControllerCallback<
393
+ O,
394
+ R,
395
+ [RequestContext, Parameters.GetParameters, ...Rest]
396
+ >,
397
+ ): DataControllerCallback<
398
+ O,
399
+ R,
400
+ [RequestContext, Parameters.GetParameters, ...Rest]
401
+ > {
402
+ return {
403
+ ...route,
404
+ func: function (this: DataControllerInstance, ctx, params, ...rest) {
405
+ console.log(params.id);
406
+ return route.func.call(this, ctx, params, ...rest);
407
+ },
408
+ };
409
+ }
410
+
411
+ const loggedGet = withLoggedId(DefaultRoutes.Get);
412
+ ```
391
413
 
392
- ## Error Handling
414
+ ## Error handling
393
415
 
394
- Default routes return standard HTTP error responses:
416
+ The default routes answer with these HTTP errors.
395
417
 
396
- | Status | Condition |
397
- | --------------------------- | ---------------------------------------------------------------- |
398
- | `400 Bad Request` | Validation fails, missing required fields, or invalid parameters |
399
- | `404 Not Found` | Record does not exist |
400
- | `500 Internal Server Error` | Database operation fails |
418
+ | Status | Condition |
419
+ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
420
+ | `400 Bad Request` | A field fails its validator, a mandatory field is missing, the body is not a JSON object, or a parameter is missing or invalid |
421
+ | `404 Not Found` | The record does not exist |
422
+ | `500 Internal Server Error` | The database operation fails, or the controller holds no model |
401
423
 
402
- Error messages are returned as plain text in the response body.
424
+ A failed mandatory or validator check answers a JSON body with a `code` and the `fields` concerned (see [validators](./5.validators.md#error-handling)). Every other error the routes raise answers its message as text, such as `Missing id.` or `Not Found`.
403
425
 
404
- ## Next Steps
426
+ ## Next steps
405
427
 
406
- See the [access rights documentation](./4.access-rights.md) to learn how to control field read/write permissions.
428
+ The [access rights page](./4.access-rights.md) covers which fields each action reads and writes.