@antelopejs/interface-data-api 0.5.0 → 0.6.1
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 +57 -37
- package/dist/components.js +36 -68
- package/dist/components.js.map +1 -1
- package/dist/index.d.ts +44 -60
- package/dist/index.js +22 -10
- package/dist/index.js.map +1 -1
- package/dist/internal/computed.js +12 -4
- package/dist/internal/computed.js.map +1 -1
- package/dist/internal/controller-types.d.ts +19 -4
- package/dist/internal/default-filter.js +11 -1
- package/dist/internal/default-filter.js.map +1 -1
- package/dist/internal/deferred-joined-stream.js +40 -27
- package/dist/internal/deferred-joined-stream.js.map +1 -1
- 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 +22 -6
- package/dist/internal/filters.d.ts +3 -1
- package/dist/internal/filters.js +9 -2
- package/dist/internal/filters.js.map +1 -1
- package/dist/internal/foreign.js +1 -1
- package/dist/internal/foreign.js.map +1 -1
- package/dist/internal/guards.d.ts +20 -1
- package/dist/internal/guards.js +42 -0
- package/dist/internal/guards.js.map +1 -1
- package/dist/internal/joined.js +1 -1
- package/dist/internal/joined.js.map +1 -1
- package/dist/internal/list.d.ts +2 -2
- package/dist/internal/list.js +10 -6
- package/dist/internal/list.js.map +1 -1
- package/dist/internal/meta-records.d.ts +2 -0
- package/dist/internal/meta-records.js +10 -5
- package/dist/internal/meta-records.js.map +1 -1
- package/dist/internal/properties.d.ts +2 -2
- package/dist/internal/properties.js +6 -4
- package/dist/internal/properties.js.map +1 -1
- 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/tables.d.ts +12 -2
- package/dist/internal/tables.js +11 -0
- package/dist/internal/tables.js.map +1 -1
- package/dist/internal/validation.d.ts +4 -3
- package/dist/internal/validation.js +18 -6
- package/dist/internal/validation.js.map +1 -1
- package/dist/metadata.d.ts +13 -13
- package/dist/metadata.js +29 -31
- package/dist/metadata.js.map +1 -1
- package/dist/tests/components/access_control.test.js +11 -3
- package/dist/tests/components/access_control.test.js.map +1 -1
- package/dist/tests/components/computed.test.js +14 -3
- package/dist/tests/components/computed.test.js.map +1 -1
- package/dist/tests/components/cross_instance.test.js +2 -2
- package/dist/tests/components/cross_instance.test.js.map +1 -1
- package/dist/tests/components/deferred_joined.test.js +1 -1
- 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 +3 -3
- package/dist/tests/components/filter_options.test.js.map +1 -1
- package/dist/tests/components/foreign_computed.test.js +15 -4
- package/dist/tests/components/foreign_computed.test.js.map +1 -1
- package/dist/tests/components/foreign_joined.test.js +16 -4
- package/dist/tests/components/foreign_joined.test.js.map +1 -1
- package/dist/tests/components/joined.test.js +26 -8
- package/dist/tests/components/joined.test.js.map +1 -1
- package/dist/tests/components/listable.test.js +31 -12
- package/dist/tests/components/listable.test.js.map +1 -1
- package/dist/tests/components/localized_sort.test.js +5 -1
- package/dist/tests/components/localized_sort.test.js.map +1 -1
- package/dist/tests/components/mandatory.test.js +1 -1
- package/dist/tests/components/mandatory.test.js.map +1 -1
- package/dist/tests/components/modifier_key.test.js +12 -4
- package/dist/tests/components/modifier_key.test.js.map +1 -1
- package/dist/tests/components/parse_body.test.js +17 -16
- package/dist/tests/components/parse_body.test.js.map +1 -1
- package/dist/tests/components/parsed_value.test.js +2 -2
- package/dist/tests/components/parsed_value.test.js.map +1 -1
- package/dist/tests/components/partial_edit.test.js +8 -3
- package/dist/tests/components/partial_edit.test.js.map +1 -1
- package/dist/tests/components/per_action_access.test.js +11 -3
- 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 +1 -1
- 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.js +8 -4
- 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/inheritance.test.js +6 -2
- 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 +14 -5
- package/dist/tests/index/routes.test.js.map +1 -1
- package/dist/tests/integration.test.js +24 -11
- 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 +21 -9
- package/dist/tests/internal/http.js +24 -3
- package/dist/tests/internal/http.js.map +1 -1
- package/dist/tests/internal/shop-schema.d.ts +28 -20
- package/dist/tests/utils.d.ts +5 -7
- package/dist/tests/utils.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 +150 -133
- package/docs/4.access-rights.md +48 -44
- package/docs/5.validators.md +54 -45
- package/docs/6.listable.md +53 -49
- package/docs/7.foreign-keys.md +55 -48
- package/docs/8.filters.md +64 -57
- package/docs/9.modifiers.md +29 -25
- package/package.json +17 -14
- package/skills/data-api-interface/SKILL.md +9 -3
package/docs/11.computed.md
CHANGED
|
@@ -1,19 +1,45 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Computed fields
|
|
3
|
+
description: Compute a field in the database with @Computed, from the row or from a per-row subquery, and sort, filter, and page on it.
|
|
4
|
+
mode: reference
|
|
5
|
+
---
|
|
2
6
|
|
|
3
7
|
## Overview
|
|
4
8
|
|
|
5
|
-
The `@Computed` decorator declares a field whose value
|
|
9
|
+
The `@Computed` decorator declares a field whose value the database computes from an expression and merges onto each row at query time. The expression can compute from other fields of the same row, or run a per-row subquery such as an aggregate over another table. The merge happens before the list query sorts and filters, so the database sorts, filters, and pages computed fields. The API never fetches every row or sorts in memory.
|
|
6
10
|
|
|
7
|
-
Computed fields are read-only
|
|
11
|
+
Computed fields are meant to be read-only. The query produces the value, and the controller's table never stores it. `@Computed` sets the field's access mode to read-only when it runs. An `@Access` written above it runs later and replaces that mode, so do not combine the two. `@Computed` also makes the field's sort non-indexed, whatever the decorator order.
|
|
8
12
|
|
|
9
|
-
## Basic
|
|
13
|
+
## Basic usage
|
|
10
14
|
|
|
11
|
-
The expression receives the row proxy and the schema instance of the controller's table. It must return a value built
|
|
15
|
+
The expression receives the row proxy and the schema instance of the controller's table. It must return a value built with the query builder (`ValueProxy` operations or a subquery), not plain JavaScript computed at request time.
|
|
12
16
|
|
|
13
|
-
The
|
|
17
|
+
The example follows the [quick-start controller pattern](./1.introduction.md#quick-start). Register the `myapp` schema before the module serves requests. Check [driver support](#driver-support) before you use aggregate subqueries.
|
|
14
18
|
|
|
15
19
|
```typescript
|
|
16
|
-
import {
|
|
20
|
+
import { Controller } from "@antelopejs/interface-api";
|
|
21
|
+
import {
|
|
22
|
+
DataController,
|
|
23
|
+
DefaultRoutes,
|
|
24
|
+
RegisterDataController,
|
|
25
|
+
} from "@antelopejs/interface-data-api";
|
|
26
|
+
import {
|
|
27
|
+
Access,
|
|
28
|
+
AccessMode,
|
|
29
|
+
Computed,
|
|
30
|
+
Listable,
|
|
31
|
+
ModelReference,
|
|
32
|
+
Sortable,
|
|
33
|
+
} from "@antelopejs/interface-data-api/metadata";
|
|
34
|
+
import {
|
|
35
|
+
BasicDataModel,
|
|
36
|
+
Field,
|
|
37
|
+
Index,
|
|
38
|
+
Model,
|
|
39
|
+
RegisterTable,
|
|
40
|
+
Relation,
|
|
41
|
+
Table,
|
|
42
|
+
} from "@antelopejs/interface-database-decorators";
|
|
17
43
|
|
|
18
44
|
@RegisterTable("groups", "myapp")
|
|
19
45
|
class Group extends Table {
|
|
@@ -35,6 +61,8 @@ class Device extends Table {
|
|
|
35
61
|
declare group_id: string;
|
|
36
62
|
}
|
|
37
63
|
|
|
64
|
+
class GroupModel extends BasicDataModel(Group, "groups") {}
|
|
65
|
+
|
|
38
66
|
@RegisterDataController()
|
|
39
67
|
class GroupAPI extends DataController(
|
|
40
68
|
Group,
|
|
@@ -56,7 +84,11 @@ class GroupAPI extends DataController(
|
|
|
56
84
|
// Aggregate: number of devices referencing this group, computed in DB
|
|
57
85
|
@Listable()
|
|
58
86
|
@Computed(
|
|
59
|
-
(row, db) =>
|
|
87
|
+
(row, db) =>
|
|
88
|
+
db
|
|
89
|
+
.table("devices")
|
|
90
|
+
.filter((device) => device.key("group_id").eq(row.key("_id")))
|
|
91
|
+
.count(),
|
|
60
92
|
{ default: 0 },
|
|
61
93
|
)
|
|
62
94
|
@Sortable()
|
|
@@ -64,11 +96,13 @@ class GroupAPI extends DataController(
|
|
|
64
96
|
|
|
65
97
|
// Row-local expression
|
|
66
98
|
@Listable()
|
|
67
|
-
@Computed((row) =>
|
|
99
|
+
@Computed((row) => row.key("name").concat("!"))
|
|
68
100
|
declare display_name: string;
|
|
69
101
|
}
|
|
70
102
|
```
|
|
71
103
|
|
|
104
|
+
The row proxy types every field as a `FieldValue`. The subquery compares each device's `group_id` with the row's `_id` through `eq`, which accepts that type. This filter checks every device for each row and does not use the `group_id` index. `getAll(row.key("_id"), "group_id")` would use the index, but `getAll` does not accept a row key typed as a `FieldValue`, so it compiles only with a cast.
|
|
105
|
+
|
|
72
106
|
A GET request to `/groups/get?id=group-123` returns the computed values at the top level:
|
|
73
107
|
|
|
74
108
|
```json
|
|
@@ -80,69 +114,79 @@ A GET request to `/groups/get?id=group-123` returns the computed values at the t
|
|
|
80
114
|
}
|
|
81
115
|
```
|
|
82
116
|
|
|
83
|
-
## Decorator
|
|
117
|
+
## Decorator parameters
|
|
84
118
|
|
|
85
|
-
```typescript
|
|
119
|
+
```typescript fragment
|
|
86
120
|
@Computed(expr, options?)
|
|
87
121
|
```
|
|
88
122
|
|
|
89
|
-
| Parameter | Type | Required | Description
|
|
90
|
-
| ----------------- | ------------------------- | -------- |
|
|
91
|
-
| `expr` | `(row, db) => expression` | Yes | Expression receiving the row proxy and the schema instance
|
|
92
|
-
| `options.default` | `
|
|
123
|
+
| Parameter | Type | Required | Description |
|
|
124
|
+
| ----------------- | ------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
125
|
+
| `expr` | `(row, db) => expression` | Yes | Expression receiving the row proxy and the schema instance. Returns a `ValueProxy` expression, a subquery (`Query`), or a constant |
|
|
126
|
+
| `options.default` | `FieldValue` | No | Fallback merged over the computed value when the expression yields `null` |
|
|
93
127
|
|
|
94
128
|
### The `default` option
|
|
95
129
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
@Computed(
|
|
100
|
-
|
|
101
|
-
|
|
130
|
+
An aggregate subquery over an empty set yields `null` instead of its zero value. A `count()` over a group with no devices returns `null`, not `0`. Calling `.default(0)` inside the subquery expression does not help, because the aggregation produces no document to apply the default to. The `options.default` value works because the Data API applies it to the row, after the merge:
|
|
131
|
+
|
|
132
|
+
```typescript fragment
|
|
133
|
+
@Computed(
|
|
134
|
+
(row, db) =>
|
|
135
|
+
db
|
|
136
|
+
.table("devices")
|
|
137
|
+
.filter((device) => device.key("group_id").eq(row.key("_id")))
|
|
138
|
+
.count(),
|
|
139
|
+
{ default: 0 },
|
|
140
|
+
)
|
|
102
141
|
declare devices_count: number;
|
|
103
142
|
```
|
|
104
143
|
|
|
105
|
-
Without it,
|
|
144
|
+
Without it, a row with an empty aggregate holds `null`. An equality filter such as `filter_devices_count=eq:0` then misses the row, and the sort order depends on BSON type ordering.
|
|
106
145
|
|
|
107
|
-
## Sorting,
|
|
146
|
+
## Sorting, filtering, and pagination
|
|
108
147
|
|
|
109
|
-
Computed fields combine with `@Sortable` and `@Filter` like
|
|
148
|
+
Computed fields combine with `@Sortable` and `@Filter` like stored columns. The list query merges each computed field only where a step needs its value:
|
|
110
149
|
|
|
111
|
-
- A computed field
|
|
112
|
-
- A computed field used as the sort key is merged after the total count
|
|
113
|
-
- A computed field requested only for display
|
|
150
|
+
- A computed field that an active filter reads is merged before the filters run.
|
|
151
|
+
- A computed field used as the sort key is merged after the total count and before the non-indexed `orderBy`, so the count does not compute the aggregate.
|
|
152
|
+
- A computed field requested only for display, in the pluck and neither sorted nor filtered, is merged after pagination (`slice`). The database computes it for the returned page only.
|
|
114
153
|
|
|
115
|
-
Sorting and pagination both stay in the database
|
|
154
|
+
Sorting and pagination both stay in the database. Sorting by a computed aggregate with `limit` and `offset` gives correct pages and the same `total` on every page.
|
|
116
155
|
|
|
117
|
-
|
|
156
|
+
A computed field always sorts without a database index, because its value is not a stored column. `@Sortable` on a computed field is always non-indexed.
|
|
118
157
|
|
|
119
158
|
### Filtering on numeric computed fields
|
|
120
159
|
|
|
121
|
-
Filter values arrive as strings from the query string.
|
|
122
|
-
|
|
123
|
-
```typescript
|
|
124
|
-
@Computed(
|
|
125
|
-
|
|
126
|
-
|
|
160
|
+
Filter values arrive as strings from the query string. Import the validated `numberFilter` from [Numeric and boolean filters](./8.filters.md#numeric-and-boolean-filters) into the controller file. It rejects invalid numbers and applies the requested comparison operator. `parseFloat` alone accepts partial input such as `"3devices"`.
|
|
161
|
+
|
|
162
|
+
```typescript fragment
|
|
163
|
+
@Computed(
|
|
164
|
+
(row, db) =>
|
|
165
|
+
db
|
|
166
|
+
.table("devices")
|
|
167
|
+
.filter((device) => device.key("group_id").eq(row.key("_id")))
|
|
168
|
+
.count(),
|
|
169
|
+
{ default: 0 },
|
|
170
|
+
)
|
|
127
171
|
@Filter(numberFilter, false)
|
|
128
172
|
declare devices_count: number;
|
|
129
173
|
```
|
|
130
174
|
|
|
131
|
-
## Computed
|
|
175
|
+
## `@Computed` compared with `@Joined`
|
|
132
176
|
|
|
133
|
-
| Aspect
|
|
134
|
-
|
|
|
135
|
-
| Source
|
|
136
|
-
| Typical use
|
|
137
|
-
| Access
|
|
138
|
-
| Sort
|
|
177
|
+
| Aspect | `@Computed` | `@Joined` |
|
|
178
|
+
| --------------- | ---------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
179
|
+
| Source | Any expression: a computation over the row or a per-row subquery | One scalar field from another table matched by a foreign key |
|
|
180
|
+
| Typical use | Aggregates (`count`, `sum`), derived values | Flattening a field of a referenced record |
|
|
181
|
+
| Access | Read-only, unless a later `@Access` replaces it | Read-only, unless a later `@Access` replaces it |
|
|
182
|
+
| Sort and filter | In the database, merged only where a step needs the value | In the database, join applied before sort and filter |
|
|
139
183
|
|
|
140
|
-
|
|
184
|
+
For one field copied from one referenced record, `@Joined` is simpler. `@Computed` applies the same merge to any expression.
|
|
141
185
|
|
|
142
|
-
## Driver
|
|
186
|
+
## Driver support
|
|
143
187
|
|
|
144
|
-
The aggregate form
|
|
188
|
+
The aggregate form needs a database driver that turns a per-row subquery inside a merge into a correlated lookup. On MongoDB, the lookup is a `$lookup` with `let` and `pipeline`. The `@antelopejs/mongodb` changelog names the release that added this translation. Row-local expressions use standard expression compilation and work on any driver.
|
|
145
189
|
|
|
146
|
-
## Next
|
|
190
|
+
## Next steps
|
|
147
191
|
|
|
148
|
-
|
|
192
|
+
For the decorator that copies one field from a referenced record, see [Joined fields](./10.joined.md).
|
|
@@ -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.
|