@antelopejs/interface-data-api 0.5.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 (139) 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 +57 -37
  5. package/dist/components.js +36 -68
  6. package/dist/components.js.map +1 -1
  7. package/dist/index.d.ts +44 -60
  8. package/dist/index.js +22 -10
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal/computed.js +12 -4
  11. package/dist/internal/computed.js.map +1 -1
  12. package/dist/internal/controller-types.d.ts +19 -4
  13. package/dist/internal/default-filter.js +11 -1
  14. package/dist/internal/default-filter.js.map +1 -1
  15. package/dist/internal/deferred-joined-stream.js +40 -27
  16. package/dist/internal/deferred-joined-stream.js.map +1 -1
  17. package/dist/internal/extractors.d.ts +25 -0
  18. package/dist/internal/extractors.js +31 -0
  19. package/dist/internal/extractors.js.map +1 -0
  20. package/dist/internal/field-types.d.ts +22 -6
  21. package/dist/internal/filters.d.ts +3 -1
  22. package/dist/internal/filters.js +9 -2
  23. package/dist/internal/filters.js.map +1 -1
  24. package/dist/internal/foreign.js +1 -1
  25. package/dist/internal/foreign.js.map +1 -1
  26. package/dist/internal/guards.d.ts +20 -1
  27. package/dist/internal/guards.js +42 -0
  28. package/dist/internal/guards.js.map +1 -1
  29. package/dist/internal/joined.js +1 -1
  30. package/dist/internal/joined.js.map +1 -1
  31. package/dist/internal/list.d.ts +2 -2
  32. package/dist/internal/list.js +10 -6
  33. package/dist/internal/list.js.map +1 -1
  34. package/dist/internal/meta-records.d.ts +2 -0
  35. package/dist/internal/meta-records.js +10 -5
  36. package/dist/internal/meta-records.js.map +1 -1
  37. package/dist/internal/properties.d.ts +2 -2
  38. package/dist/internal/properties.js +6 -4
  39. package/dist/internal/properties.js.map +1 -1
  40. package/dist/internal/prototypes.d.ts +14 -0
  41. package/dist/internal/prototypes.js +38 -0
  42. package/dist/internal/prototypes.js.map +1 -0
  43. package/dist/internal/read-properties.d.ts +3 -0
  44. package/dist/internal/read-properties.js +29 -0
  45. package/dist/internal/read-properties.js.map +1 -0
  46. package/dist/internal/tables.d.ts +12 -2
  47. package/dist/internal/tables.js +11 -0
  48. package/dist/internal/tables.js.map +1 -1
  49. package/dist/internal/validation.d.ts +4 -3
  50. package/dist/internal/validation.js +18 -6
  51. package/dist/internal/validation.js.map +1 -1
  52. package/dist/metadata.d.ts +13 -13
  53. package/dist/metadata.js +29 -31
  54. package/dist/metadata.js.map +1 -1
  55. package/dist/tests/components/access_control.test.js +11 -3
  56. package/dist/tests/components/access_control.test.js.map +1 -1
  57. package/dist/tests/components/computed.test.js +14 -3
  58. package/dist/tests/components/computed.test.js.map +1 -1
  59. package/dist/tests/components/cross_instance.test.js +2 -2
  60. package/dist/tests/components/cross_instance.test.js.map +1 -1
  61. package/dist/tests/components/deferred_joined.test.js +1 -1
  62. package/dist/tests/components/deferred_joined.test.js.map +1 -1
  63. package/dist/tests/components/extract_generic.test.d.ts +1 -0
  64. package/dist/tests/components/extract_generic.test.js +64 -0
  65. package/dist/tests/components/extract_generic.test.js.map +1 -0
  66. package/dist/tests/components/filter_comparisons.test.d.ts +1 -0
  67. package/dist/tests/components/filter_comparisons.test.js +34 -0
  68. package/dist/tests/components/filter_comparisons.test.js.map +1 -0
  69. package/dist/tests/components/filter_list_route.test.d.ts +1 -0
  70. package/dist/tests/components/filter_list_route.test.js +182 -0
  71. package/dist/tests/components/filter_list_route.test.js.map +1 -0
  72. package/dist/tests/components/filter_options.test.js +3 -3
  73. package/dist/tests/components/filter_options.test.js.map +1 -1
  74. package/dist/tests/components/foreign_computed.test.js +15 -4
  75. package/dist/tests/components/foreign_computed.test.js.map +1 -1
  76. package/dist/tests/components/foreign_joined.test.js +16 -4
  77. package/dist/tests/components/foreign_joined.test.js.map +1 -1
  78. package/dist/tests/components/joined.test.js +26 -8
  79. package/dist/tests/components/joined.test.js.map +1 -1
  80. package/dist/tests/components/listable.test.js +31 -12
  81. package/dist/tests/components/listable.test.js.map +1 -1
  82. package/dist/tests/components/localized_sort.test.js +5 -1
  83. package/dist/tests/components/localized_sort.test.js.map +1 -1
  84. package/dist/tests/components/mandatory.test.js +1 -1
  85. package/dist/tests/components/mandatory.test.js.map +1 -1
  86. package/dist/tests/components/modifier_key.test.js +12 -4
  87. package/dist/tests/components/modifier_key.test.js.map +1 -1
  88. package/dist/tests/components/parse_body.test.js +17 -16
  89. package/dist/tests/components/parse_body.test.js.map +1 -1
  90. package/dist/tests/components/parsed_value.test.js +2 -2
  91. package/dist/tests/components/parsed_value.test.js.map +1 -1
  92. package/dist/tests/components/partial_edit.test.js +8 -3
  93. package/dist/tests/components/partial_edit.test.js.map +1 -1
  94. package/dist/tests/components/per_action_access.test.js +11 -3
  95. package/dist/tests/components/per_action_access.test.js.map +1 -1
  96. package/dist/tests/components/prototype_named_filter.test.d.ts +1 -0
  97. package/dist/tests/components/prototype_named_filter.test.js +87 -0
  98. package/dist/tests/components/prototype_named_filter.test.js.map +1 -0
  99. package/dist/tests/components/validator.test.js +1 -1
  100. package/dist/tests/components/validator.test.js.map +1 -1
  101. package/dist/tests/components/written_row.test.d.ts +1 -0
  102. package/dist/tests/components/written_row.test.js +99 -0
  103. package/dist/tests/components/written_row.test.js.map +1 -0
  104. package/dist/tests/helpers/other-instance.js +8 -4
  105. package/dist/tests/helpers/other-instance.js.map +1 -1
  106. package/dist/tests/index/field_decorator_target.test.d.ts +1 -0
  107. package/dist/tests/index/field_decorator_target.test.js +20 -0
  108. package/dist/tests/index/field_decorator_target.test.js.map +1 -0
  109. package/dist/tests/index/inheritance.test.js +6 -2
  110. package/dist/tests/index/inheritance.test.js.map +1 -1
  111. package/dist/tests/index/metadata_names.test.d.ts +1 -0
  112. package/dist/tests/index/metadata_names.test.js +91 -0
  113. package/dist/tests/index/metadata_names.test.js.map +1 -0
  114. package/dist/tests/index/routes.test.js +14 -5
  115. package/dist/tests/index/routes.test.js.map +1 -1
  116. package/dist/tests/integration.test.js +24 -11
  117. package/dist/tests/integration.test.js.map +1 -1
  118. package/dist/tests/internal/controller-fields.test.d.ts +1 -0
  119. package/dist/tests/internal/controller-fields.test.js +25 -0
  120. package/dist/tests/internal/controller-fields.test.js.map +1 -0
  121. package/dist/tests/internal/http.d.ts +21 -9
  122. package/dist/tests/internal/http.js +24 -3
  123. package/dist/tests/internal/http.js.map +1 -1
  124. package/dist/tests/internal/shop-schema.d.ts +28 -20
  125. package/dist/tests/utils.d.ts +5 -7
  126. package/dist/tests/utils.js.map +1 -1
  127. package/docs/1.introduction.md +38 -35
  128. package/docs/10.joined.md +51 -40
  129. package/docs/11.computed.md +91 -47
  130. package/docs/2.data-controllers.md +50 -42
  131. package/docs/3.routes.md +150 -133
  132. package/docs/4.access-rights.md +48 -44
  133. package/docs/5.validators.md +54 -45
  134. package/docs/6.listable.md +53 -49
  135. package/docs/7.foreign-keys.md +55 -48
  136. package/docs/8.filters.md +64 -57
  137. package/docs/9.modifiers.md +29 -25
  138. package/package.json +24 -14
  139. package/skills/data-api-interface/SKILL.md +9 -3
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`
63
+ Reads one record by its identifier. The endpoint is `GET /resource/get`.
62
64
 
63
- **Query Parameters:**
65
+ | Query parameter | Required | Description |
66
+ | --------------- | -------- | ----------------- |
67
+ | `id` | Yes | Record identifier |
64
68
 
65
- | Parameter | Required | Description |
66
- | --------- | -------- | ----------------- |
67
- | `id` | Yes | Record identifier |
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`
87
+ Reads one page of records, with sorting and filtering. The endpoint is `GET /resource/list`.
90
88
 
91
- **Query Parameters:**
92
-
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.
158
-
159
- **Endpoint:** `PUT /resource/edit`
146
+ Updates a record by its identifier. The endpoint is `PUT /resource/edit`.
160
147
 
161
- **Query Parameters:**
148
+ | Query parameter | Required | Description |
149
+ | --------------- | -------- | ----------------- |
150
+ | `id` | Yes | Record identifier |
162
151
 
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.
176
-
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`.
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.
178
161
 
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.
188
+ Deletes one or more records by their identifiers. The endpoint is `DELETE /resource/delete`.
206
189
 
207
- **Endpoint:** `DELETE /resource/delete`
190
+ | Query parameter | Required | Description |
191
+ | --------------- | -------- | ----------------------------------------------------------------------------------- |
192
+ | `id` | Yes | Record identifier. Repeat the `id` parameter to delete several records in one call. |
208
193
 
209
- **Query Parameters:**
210
-
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,48 +229,52 @@ 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.
293
274
 
294
- A parsed body is `FieldValues`: fields by name, each holding a `FieldValue` (a string, number, boolean, `null`, `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: a function, a class instance, a `bigint`, a cycle or an `undefined` array element. The default routes run with the controller as `this`, so a wrapping function declares `this: DataControllerInstance` and calls `route.func.call(this, ...)`.
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, ...)`.
295
278
 
296
279
  ```ts docs-check
297
280
  import {
@@ -319,9 +302,9 @@ const stampedNew = {
319
302
  };
320
303
  ```
321
304
 
322
- ## Loaded-Model Guards with `WithGetGuard`
305
+ ## Loaded-model guards with `WithGetGuard`
323
306
 
324
- `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.
325
308
 
326
309
  ```ts docs-check
327
310
  import { DefaultRoutes } from "@antelopejs/interface-data-api";
@@ -329,7 +312,7 @@ import { assert } from "@antelopejs/interface-api-util";
329
312
 
330
313
  const invoiceGet = DefaultRoutes.WithGetGuard(
331
314
  async function (ctx, current, params) {
332
- // documentType can be stored without being exposed through @Access.
315
+ // The table stores documentType, and no @Access exposes it.
333
316
  assert(current.documentType === "invoice", 404, "Not Found");
334
317
  },
335
318
  );
@@ -340,28 +323,28 @@ const routes = {
340
323
  };
341
324
  ```
342
325
 
343
- The `GetGuard` callback receives `(ctx, current, params)`. Declare it as a regular function to access the controller through `this`, typed `DataControllerInstance`. `current` is `FieldValues`.
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`.
344
327
 
345
- | Argument | Contents | Use it for |
346
- | --------- | --------------------------------------------------------------------------------------- | ---------------------------------------------- |
347
- | `ctx` | Request context | Request-specific policy. |
348
- | `current` | Loaded model, including non-readable fields and normal Get joined/computed/foreign data | Inspecting stored ownership or discriminators. |
349
- | `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 |
350
333
 
351
- 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.
352
335
 
353
- Keep these boundaries when writing a guard:
336
+ A guard follows these rules.
354
337
 
355
- - **Deny by throwing.** Throw an `HTTPResult` or use `assert`. A normal return permits the read, including a return value of `false`.
356
- - **Missing records bypass the guard.** The route returns `404` without invoking it.
357
- - **Use the already-loaded model.** The guard does not issue a second read or change field-access rules.
358
- - **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.
359
342
 
360
- ## Custom Routes
343
+ ## Custom routes
361
344
 
362
- 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.
363
346
 
364
- ```typescript
347
+ ```typescript fragment
365
348
  import { Context } from "@antelopejs/interface-api";
366
349
  import {
367
350
  DataController,
@@ -392,20 +375,54 @@ class UserAPI extends DataController(
392
375
  }
393
376
  ```
394
377
 
395
- 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
+ ```
396
413
 
397
- ## Error Handling
414
+ ## Error handling
398
415
 
399
- Default routes return standard HTTP error responses:
416
+ The default routes answer with these HTTP errors.
400
417
 
401
- | Status | Condition |
402
- | --------------------------- | ---------------------------------------------------------------- |
403
- | `400 Bad Request` | Validation fails, missing required fields, or invalid parameters |
404
- | `404 Not Found` | Record does not exist |
405
- | `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 |
406
423
 
407
- 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`.
408
425
 
409
- ## Next Steps
426
+ ## Next steps
410
427
 
411
- 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.