@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.
- package/README.md +26 -22
- package/dist/antelope.test.js +1 -5
- package/dist/antelope.test.js.map +1 -1
- package/dist/components.d.ts +67 -39
- package/dist/components.js +106 -685
- package/dist/components.js.map +1 -1
- package/dist/index.d.ts +57 -68
- package/dist/index.js +114 -100
- package/dist/index.js.map +1 -1
- package/dist/internal/computed.d.ts +6 -0
- package/dist/internal/computed.js +51 -0
- package/dist/internal/computed.js.map +1 -0
- package/dist/internal/controller-types.d.ts +34 -0
- package/dist/internal/controller-types.js +4 -0
- package/dist/internal/controller-types.js.map +1 -0
- package/dist/internal/default-filter.d.ts +4 -0
- package/dist/internal/default-filter.js +29 -0
- package/dist/internal/default-filter.js.map +1 -0
- package/dist/internal/deferred-joined-stream.d.ts +11 -0
- package/dist/internal/deferred-joined-stream.js +125 -0
- package/dist/internal/deferred-joined-stream.js.map +1 -0
- package/dist/internal/extractors.d.ts +25 -0
- package/dist/internal/extractors.js +31 -0
- package/dist/internal/extractors.js.map +1 -0
- package/dist/internal/field-types.d.ts +199 -0
- package/dist/internal/field-types.js +4 -0
- package/dist/internal/field-types.js.map +1 -0
- package/dist/internal/filters.d.ts +6 -0
- package/dist/internal/filters.js +22 -0
- package/dist/internal/filters.js.map +1 -0
- package/dist/internal/foreign.d.ts +6 -0
- package/dist/internal/foreign.js +116 -0
- package/dist/internal/foreign.js.map +1 -0
- package/dist/internal/guards.d.ts +39 -0
- package/dist/internal/guards.js +116 -0
- package/dist/internal/guards.js.map +1 -0
- package/dist/internal/joined.d.ts +28 -0
- package/dist/internal/joined.js +127 -0
- package/dist/internal/joined.js.map +1 -0
- package/dist/internal/list.d.ts +16 -0
- package/dist/internal/list.js +182 -0
- package/dist/internal/list.js.map +1 -0
- package/dist/internal/meta-records.d.ts +19 -0
- package/dist/internal/meta-records.js +78 -0
- package/dist/internal/meta-records.js.map +1 -0
- package/dist/internal/properties.d.ts +4 -0
- package/dist/internal/properties.js +45 -0
- package/dist/internal/properties.js.map +1 -0
- package/dist/internal/prototypes.d.ts +14 -0
- package/dist/internal/prototypes.js +38 -0
- package/dist/internal/prototypes.js.map +1 -0
- package/dist/internal/read-properties.d.ts +3 -0
- package/dist/internal/read-properties.js +29 -0
- package/dist/internal/read-properties.js.map +1 -0
- package/dist/internal/table-reference.d.ts +11 -0
- package/dist/internal/table-reference.js +46 -0
- package/dist/internal/table-reference.js.map +1 -0
- package/dist/internal/tables.d.ts +28 -0
- package/dist/internal/tables.js +48 -0
- package/dist/internal/tables.js.map +1 -0
- package/dist/internal/validation.d.ts +12 -0
- package/dist/internal/validation.js +100 -0
- package/dist/internal/validation.js.map +1 -0
- package/dist/metadata.d.ts +27 -228
- package/dist/metadata.js +84 -213
- package/dist/metadata.js.map +1 -1
- package/dist/tests/components/access_control.test.js +28 -20
- package/dist/tests/components/access_control.test.js.map +1 -1
- package/dist/tests/components/computed.test.js +36 -27
- package/dist/tests/components/computed.test.js.map +1 -1
- package/dist/tests/components/cross_instance.test.js +8 -6
- package/dist/tests/components/cross_instance.test.js.map +1 -1
- package/dist/tests/components/deferred_joined.test.js +57 -54
- package/dist/tests/components/deferred_joined.test.js.map +1 -1
- package/dist/tests/components/extract_generic.test.d.ts +1 -0
- package/dist/tests/components/extract_generic.test.js +64 -0
- package/dist/tests/components/extract_generic.test.js.map +1 -0
- package/dist/tests/components/filter_comparisons.test.d.ts +1 -0
- package/dist/tests/components/filter_comparisons.test.js +34 -0
- package/dist/tests/components/filter_comparisons.test.js.map +1 -0
- package/dist/tests/components/filter_list_route.test.d.ts +1 -0
- package/dist/tests/components/filter_list_route.test.js +182 -0
- package/dist/tests/components/filter_list_route.test.js.map +1 -0
- package/dist/tests/components/filter_options.test.js +11 -10
- package/dist/tests/components/filter_options.test.js.map +1 -1
- package/dist/tests/components/foreign_computed.test.js +23 -14
- package/dist/tests/components/foreign_computed.test.js.map +1 -1
- package/dist/tests/components/foreign_joined.test.js +31 -27
- package/dist/tests/components/foreign_joined.test.js.map +1 -1
- package/dist/tests/components/joined.test.js +59 -45
- package/dist/tests/components/joined.test.js.map +1 -1
- package/dist/tests/components/listable.test.js +78 -58
- package/dist/tests/components/listable.test.js.map +1 -1
- package/dist/tests/components/localized_sort.test.js +14 -11
- package/dist/tests/components/localized_sort.test.js.map +1 -1
- package/dist/tests/components/mandatory.test.js +25 -25
- package/dist/tests/components/mandatory.test.js.map +1 -1
- package/dist/tests/components/modifier_key.test.js +27 -18
- package/dist/tests/components/modifier_key.test.js.map +1 -1
- package/dist/tests/components/parse_body.test.d.ts +1 -0
- package/dist/tests/components/parse_body.test.js +76 -0
- package/dist/tests/components/parse_body.test.js.map +1 -0
- package/dist/tests/components/parsed_value.test.js +23 -21
- package/dist/tests/components/parsed_value.test.js.map +1 -1
- package/dist/tests/components/partial_edit.test.js +38 -32
- package/dist/tests/components/partial_edit.test.js.map +1 -1
- package/dist/tests/components/per_action_access.test.js +20 -12
- package/dist/tests/components/per_action_access.test.js.map +1 -1
- package/dist/tests/components/prototype_named_filter.test.d.ts +1 -0
- package/dist/tests/components/prototype_named_filter.test.js +87 -0
- package/dist/tests/components/prototype_named_filter.test.js.map +1 -0
- package/dist/tests/components/validator.test.js +45 -37
- package/dist/tests/components/validator.test.js.map +1 -1
- package/dist/tests/components/written_row.test.d.ts +1 -0
- package/dist/tests/components/written_row.test.js +99 -0
- package/dist/tests/components/written_row.test.js.map +1 -0
- package/dist/tests/helpers/other-instance.d.ts +11 -1
- package/dist/tests/helpers/other-instance.js +35 -9
- package/dist/tests/helpers/other-instance.js.map +1 -1
- package/dist/tests/index/field_decorator_target.test.d.ts +1 -0
- package/dist/tests/index/field_decorator_target.test.js +20 -0
- package/dist/tests/index/field_decorator_target.test.js.map +1 -0
- package/dist/tests/index/get_guard.test.js +6 -6
- package/dist/tests/index/get_guard.test.js.map +1 -1
- package/dist/tests/index/inheritance.test.js +14 -9
- package/dist/tests/index/inheritance.test.js.map +1 -1
- package/dist/tests/index/metadata_names.test.d.ts +1 -0
- package/dist/tests/index/metadata_names.test.js +91 -0
- package/dist/tests/index/metadata_names.test.js.map +1 -0
- package/dist/tests/index/routes.test.js +49 -44
- package/dist/tests/index/routes.test.js.map +1 -1
- package/dist/tests/integration.test.d.ts +1 -1
- package/dist/tests/integration.test.js +66 -729
- package/dist/tests/integration.test.js.map +1 -1
- package/dist/tests/internal/controller-fields.test.d.ts +1 -0
- package/dist/tests/internal/controller-fields.test.js +25 -0
- package/dist/tests/internal/controller-fields.test.js.map +1 -0
- package/dist/tests/internal/http.d.ts +35 -0
- package/dist/tests/internal/http.js +56 -0
- package/dist/tests/internal/http.js.map +1 -0
- package/dist/tests/internal/shop-api.d.ts +1 -0
- package/dist/tests/internal/shop-api.js +331 -0
- package/dist/tests/internal/shop-api.js.map +1 -0
- package/dist/tests/internal/shop-schema.d.ts +162 -0
- package/dist/tests/internal/shop-schema.js +387 -0
- package/dist/tests/internal/shop-schema.js.map +1 -0
- package/dist/tests/utils.d.ts +8 -10
- package/dist/tests/utils.js +1 -3
- package/dist/tests/utils.js.map +1 -1
- package/dist/tests/utils.test.js +10 -11
- package/dist/tests/utils.test.js.map +1 -1
- package/docs/1.introduction.md +38 -35
- package/docs/10.joined.md +51 -40
- package/docs/11.computed.md +91 -47
- package/docs/2.data-controllers.md +50 -42
- package/docs/3.routes.md +156 -134
- package/docs/4.access-rights.md +48 -44
- package/docs/5.validators.md +65 -47
- package/docs/6.listable.md +53 -49
- package/docs/7.foreign-keys.md +55 -48
- package/docs/8.filters.md +65 -54
- package/docs/9.modifiers.md +29 -25
- package/package.json +34 -13
- package/skills/data-api-interface/SKILL.md +13 -3
- package/skills/data-api-interface/cases.json +99 -0
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
|
11
|
+
## Create a data controller
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
`DataController` takes three arguments: the table class, a route definition object, and a controller base with the endpoint path.
|
|
10
14
|
|
|
11
|
-
|
|
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`
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
DataController
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
87
|
-
| `
|
|
88
|
-
| `base` | The controller base,
|
|
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
|
|
97
|
+
## Required components
|
|
91
98
|
|
|
92
|
-
Every data controller
|
|
99
|
+
Every data controller declares these four parts.
|
|
93
100
|
|
|
94
|
-
1. **Table
|
|
95
|
-
2. **Route
|
|
96
|
-
3. **Controller
|
|
97
|
-
4. **Model
|
|
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`
|
|
106
|
+
## The `@RegisterDataController` decorator
|
|
100
107
|
|
|
101
|
-
Apply `@RegisterDataController()` to the class to
|
|
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
|
|
110
|
+
## Field declarations
|
|
104
111
|
|
|
105
|
-
|
|
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`
|
|
108
|
-
- `@Listable`
|
|
109
|
-
- `@Mandatory`
|
|
110
|
-
- `@Validator`
|
|
111
|
-
- `@Foreign`
|
|
112
|
-
- `@Joined`
|
|
113
|
-
- `@
|
|
114
|
-
- `@
|
|
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
|
-
|
|
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
|
|
126
|
+
## Next steps
|
|
119
127
|
|
|
120
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
11
|
+
## Default routes
|
|
8
12
|
|
|
9
|
-
The `DefaultRoutes` namespace
|
|
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
|
|
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
|
-
|
|
27
|
+
`DefaultRoutes.All` holds all five routes under the keys `get`, `list`, `new`, `edit` and `delete`.
|
|
24
28
|
|
|
25
|
-
## Route
|
|
29
|
+
## Route configuration
|
|
26
30
|
|
|
27
|
-
|
|
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
|
|
55
|
+
// no edit or delete endpoint
|
|
52
56
|
}
|
|
53
57
|
```
|
|
54
58
|
|
|
55
|
-
## Route
|
|
59
|
+
## Route details
|
|
56
60
|
|
|
57
61
|
### Get
|
|
58
62
|
|
|
59
|
-
|
|
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
|
-
|
|
|
66
|
-
|
|
|
67
|
-
| `id`
|
|
65
|
+
| Query parameter | Required | Description |
|
|
66
|
+
| --------------- | -------- | ----------------- |
|
|
67
|
+
| `id` | Yes | Record identifier |
|
|
68
68
|
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
|
94
|
-
|
|
|
95
|
-
| `limit`
|
|
96
|
-
| `offset`
|
|
97
|
-
| `sortKey`
|
|
98
|
-
| `sortDirection`
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
146
|
+
Updates a record by its identifier. The endpoint is `PUT /resource/edit`.
|
|
158
147
|
|
|
159
|
-
|
|
148
|
+
| Query parameter | Required | Description |
|
|
149
|
+
| --------------- | -------- | ----------------- |
|
|
150
|
+
| `id` | Yes | Record identifier |
|
|
160
151
|
|
|
161
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
206
|
+
## Route options with `WithOptions`
|
|
228
207
|
|
|
229
|
-
|
|
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
|
|
238
|
-
| `options` | An object
|
|
239
|
-
| `endpoint` |
|
|
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
|
|
254
|
-
// /users/quickEdit skips mandatory field
|
|
232
|
+
// /users/edit checks mandatory fields
|
|
233
|
+
// /users/quickEdit skips the mandatory field check
|
|
255
234
|
}
|
|
256
235
|
```
|
|
257
236
|
|
|
258
|
-
### Available
|
|
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
|
-
|
|
241
|
+
The Get route takes these options.
|
|
261
242
|
|
|
262
|
-
| Option | Type | Description
|
|
263
|
-
| ----------- | -------- |
|
|
264
|
-
| `index` | `string` |
|
|
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
|
-
|
|
248
|
+
The List route takes these options.
|
|
268
249
|
|
|
269
250
|
| Option | Type | Description |
|
|
270
251
|
| ----------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
271
|
-
| `maxPage` | `number` | Maximum
|
|
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` |
|
|
274
|
-
| `pluckMode` | `string` | Select a named pluck mode (see `@Listable`)
|
|
275
|
-
| `filters` | `Record<string, FieldFilter>` | Mandatory filters merged with request 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
|
-
|
|
258
|
+
The New route takes this option.
|
|
278
259
|
|
|
279
|
-
| Option | Type | Description
|
|
280
|
-
| ------------- | -------- |
|
|
281
|
-
| `noMandatory` | `string` | Skip mandatory field
|
|
260
|
+
| Option | Type | Description |
|
|
261
|
+
| ------------- | -------- | ------------------------------ |
|
|
262
|
+
| `noMandatory` | `string` | Skip the mandatory field check |
|
|
282
263
|
|
|
283
|
-
|
|
264
|
+
The Edit route takes these options.
|
|
284
265
|
|
|
285
|
-
| Option | Type | Description
|
|
286
|
-
| ------------- | -------- |
|
|
287
|
-
| `index` | `string` |
|
|
288
|
-
| `noMandatory` | `string` | Skip mandatory field
|
|
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
|
|
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
|
|
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 {
|
|
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:
|
|
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-
|
|
305
|
+
## Loaded-model guards with `WithGetGuard`
|
|
318
306
|
|
|
319
|
-
`DefaultRoutes.WithGetGuard(guard)` returns a Get route that awaits a guard after
|
|
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
|
-
//
|
|
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
|
|
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
|
|
341
|
-
| --------- |
|
|
342
|
-
| `ctx` | Request context
|
|
343
|
-
| `current` | Loaded model,
|
|
344
|
-
| `params` | `Parameters.GetParameters`
|
|
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
|
|
334
|
+
Treat `current` as read-only. It is not the response object, and the API does not freeze it.
|
|
347
335
|
|
|
348
|
-
|
|
336
|
+
A guard follows these rules.
|
|
349
337
|
|
|
350
|
-
- **Deny by throwing.** Throw an `HTTPResult` or
|
|
351
|
-
- **
|
|
352
|
-
- **
|
|
353
|
-
- **
|
|
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
|
|
343
|
+
## Custom routes
|
|
356
344
|
|
|
357
|
-
For operations beyond CRUD, define a
|
|
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
|
|
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
|
|
414
|
+
## Error handling
|
|
393
415
|
|
|
394
|
-
|
|
416
|
+
The default routes answer with these HTTP errors.
|
|
395
417
|
|
|
396
|
-
| Status | Condition
|
|
397
|
-
| --------------------------- |
|
|
398
|
-
| `400 Bad Request` |
|
|
399
|
-
| `404 Not Found` |
|
|
400
|
-
| `500 Internal Server Error` |
|
|
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
|
-
|
|
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
|
|
426
|
+
## Next steps
|
|
405
427
|
|
|
406
|
-
|
|
428
|
+
The [access rights page](./4.access-rights.md) covers which fields each action reads and writes.
|