@antelopejs/interface-data-api 0.3.0 → 0.5.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 +1 -1
- package/dist/components.d.ts +32 -9
- package/dist/components.js +113 -628
- package/dist/components.js.map +1 -1
- package/dist/index.d.ts +41 -36
- package/dist/index.js +94 -92
- package/dist/index.js.map +1 -1
- package/dist/internal/computed.d.ts +6 -0
- package/dist/internal/computed.js +43 -0
- package/dist/internal/computed.js.map +1 -0
- package/dist/internal/controller-types.d.ts +19 -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 +19 -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 +112 -0
- package/dist/internal/deferred-joined-stream.js.map +1 -0
- package/dist/internal/field-types.d.ts +183 -0
- package/dist/internal/field-types.js +4 -0
- package/dist/internal/field-types.js.map +1 -0
- package/dist/internal/filters.d.ts +4 -0
- package/dist/internal/filters.js +15 -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 +20 -0
- package/dist/internal/guards.js +74 -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 +178 -0
- package/dist/internal/list.js.map +1 -0
- package/dist/internal/meta-records.d.ts +17 -0
- package/dist/internal/meta-records.js +73 -0
- package/dist/internal/meta-records.js.map +1 -0
- package/dist/internal/properties.d.ts +4 -0
- package/dist/internal/properties.js +43 -0
- package/dist/internal/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 +18 -0
- package/dist/internal/tables.js +37 -0
- package/dist/internal/tables.js.map +1 -0
- package/dist/internal/validation.d.ts +11 -0
- package/dist/internal/validation.js +88 -0
- package/dist/internal/validation.js.map +1 -0
- package/dist/metadata.d.ts +27 -228
- package/dist/metadata.js +61 -188
- package/dist/metadata.js.map +1 -1
- package/dist/tests/components/access_control.test.js +20 -20
- package/dist/tests/components/access_control.test.js.map +1 -1
- package/dist/tests/components/computed.test.js +28 -30
- 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 +56 -53
- package/dist/tests/components/deferred_joined.test.js.map +1 -1
- package/dist/tests/components/filter_options.test.js +8 -7
- package/dist/tests/components/filter_options.test.js.map +1 -1
- package/dist/tests/components/foreign_computed.test.js +14 -16
- package/dist/tests/components/foreign_computed.test.js.map +1 -1
- package/dist/tests/components/foreign_joined.test.js +19 -27
- package/dist/tests/components/foreign_joined.test.js.map +1 -1
- package/dist/tests/components/joined.test.js +41 -45
- package/dist/tests/components/joined.test.js.map +1 -1
- package/dist/tests/components/listable.test.js +59 -58
- package/dist/tests/components/listable.test.js.map +1 -1
- package/dist/tests/components/localized_sort.test.d.ts +1 -0
- package/dist/tests/components/localized_sort.test.js +131 -0
- package/dist/tests/components/localized_sort.test.js.map +1 -0
- package/dist/tests/components/mandatory.test.js +33 -28
- package/dist/tests/components/mandatory.test.js.map +1 -1
- package/dist/tests/components/modifier_key.test.js +19 -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 +75 -0
- package/dist/tests/components/parse_body.test.js.map +1 -0
- package/dist/tests/components/parsed_value.test.js +27 -21
- package/dist/tests/components/parsed_value.test.js.map +1 -1
- package/dist/tests/components/partial_edit.test.js +33 -32
- package/dist/tests/components/partial_edit.test.js.map +1 -1
- package/dist/tests/components/per_action_access.test.js +12 -12
- package/dist/tests/components/per_action_access.test.js.map +1 -1
- package/dist/tests/components/validator.test.js +45 -37
- package/dist/tests/components/validator.test.js.map +1 -1
- package/dist/tests/helpers/other-instance.d.ts +11 -1
- package/dist/tests/helpers/other-instance.js +27 -5
- package/dist/tests/helpers/other-instance.js.map +1 -1
- 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 +10 -9
- package/dist/tests/index/inheritance.test.js.map +1 -1
- package/dist/tests/index/routes.test.js +40 -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 +53 -729
- package/dist/tests/integration.test.js.map +1 -1
- package/dist/tests/internal/http.d.ts +23 -0
- package/dist/tests/internal/http.js +35 -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 +154 -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 +5 -5
- 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/3.routes.md +8 -3
- package/docs/5.validators.md +16 -7
- package/docs/6.listable.md +4 -0
- package/docs/8.filters.md +7 -3
- package/package.json +17 -6
- package/skills/data-api-interface/SKILL.md +4 -0
- package/skills/data-api-interface/cases.json +99 -0
package/docs/3.routes.md
CHANGED
|
@@ -291,8 +291,13 @@ class UserAPI extends DataController(User, routes, Controller("/users")) {
|
|
|
291
291
|
|
|
292
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.
|
|
293
293
|
|
|
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, ...)`.
|
|
295
|
+
|
|
294
296
|
```ts docs-check
|
|
295
|
-
import {
|
|
297
|
+
import {
|
|
298
|
+
DefaultRoutes,
|
|
299
|
+
type DataControllerInstance,
|
|
300
|
+
} from "@antelopejs/interface-data-api";
|
|
296
301
|
import {
|
|
297
302
|
type Parameters,
|
|
298
303
|
Validation,
|
|
@@ -302,7 +307,7 @@ import type { RequestContext } from "@antelopejs/interface-api";
|
|
|
302
307
|
const stampedNew = {
|
|
303
308
|
...DefaultRoutes.New,
|
|
304
309
|
func: function (
|
|
305
|
-
this:
|
|
310
|
+
this: DataControllerInstance,
|
|
306
311
|
ctx: RequestContext,
|
|
307
312
|
params: Parameters.NewParameters,
|
|
308
313
|
body: Buffer,
|
|
@@ -335,7 +340,7 @@ const routes = {
|
|
|
335
340
|
};
|
|
336
341
|
```
|
|
337
342
|
|
|
338
|
-
The `GetGuard` callback receives `(ctx, current, params)`. Declare it as a regular function to access the controller through `this`.
|
|
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`.
|
|
339
344
|
|
|
340
345
|
| Argument | Contents | Use it for |
|
|
341
346
|
| --------- | --------------------------------------------------------------------------------------- | ---------------------------------------------- |
|
package/docs/5.validators.md
CHANGED
|
@@ -36,17 +36,26 @@ class UserAPI extends DataController(
|
|
|
36
36
|
### Validation Function Signature
|
|
37
37
|
|
|
38
38
|
```typescript
|
|
39
|
+
type FieldValue =
|
|
40
|
+
string | number | boolean | null | Date | FieldValue[] | FieldValues;
|
|
41
|
+
|
|
42
|
+
interface FieldValues {
|
|
43
|
+
[field: string]: FieldValue | undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
39
46
|
type FieldValidator = (
|
|
40
|
-
value:
|
|
47
|
+
value: FieldValue | undefined,
|
|
41
48
|
) => FieldValidationResult | Promise<FieldValidationResult>;
|
|
42
49
|
|
|
43
50
|
type FieldValidationResult =
|
|
44
|
-
|
|
51
|
+
| boolean
|
|
52
|
+
| { success: true; data: FieldValue | undefined }
|
|
53
|
+
| { success: false };
|
|
45
54
|
```
|
|
46
55
|
|
|
47
56
|
The function:
|
|
48
57
|
|
|
49
|
-
- Receives the raw field value from the request body.
|
|
58
|
+
- Receives the raw field value from the request body: JSON, or a `Date` or `undefined` when a route wrapping `new` or `edit` amended the parsed body. `FieldValue | undefined` has to be narrowed before use.
|
|
50
59
|
- Returns `true` when the value is valid: the raw value is written.
|
|
51
60
|
- Returns `{ success: true, data }` when the value is valid: `data` is written instead of the raw value.
|
|
52
61
|
- Returns `false` or `{ success: false }` when the value is invalid, which triggers a `400 Bad Request` response.
|
|
@@ -215,12 +224,12 @@ declare notes: string;
|
|
|
215
224
|
|
|
216
225
|
## Error Handling
|
|
217
226
|
|
|
218
|
-
When validation or mandatory checks fail, the API returns a `400 Bad Request` response
|
|
227
|
+
When validation or mandatory checks fail, the API returns a `400 Bad Request` response whose JSON body names what went wrong and the fields concerned, for the client to word it and show it under each field:
|
|
219
228
|
|
|
220
|
-
- **Mandatory failure:** `"
|
|
221
|
-
- **Validator failure:** `"
|
|
229
|
+
- **Mandatory failure:** `{ "code": "missing_mandatory_fields", "fields": ["email", "password"] }`
|
|
230
|
+
- **Validator failure:** `{ "code": "invalid_field_types", "fields": ["age", "email"] }`
|
|
222
231
|
|
|
223
|
-
|
|
232
|
+
The codes are exported as `Validation.MISSING_MANDATORY_FIELDS` and `Validation.INVALID_FIELD_TYPES` (`@antelopejs/interface-data-api/components`), the body's type as `Validation.FieldsErrorBody`. All fields are checked: `fields` lists every one that failed.
|
|
224
233
|
|
|
225
234
|
## Next Steps
|
|
226
235
|
|
package/docs/6.listable.md
CHANGED
|
@@ -234,6 +234,10 @@ declare _id: string;
|
|
|
234
234
|
declare name: string;
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
+
### Localized Fields
|
|
238
|
+
|
|
239
|
+
A `@Localized` field is sorted on its value in the request's locale, the one the controller's `@ModifierKey(LocalizationModifier)` field holds. A row with no value in that locale, or an empty one, sorts on its `fallbackLocale` value. This ordering is computed per row, so it never uses an index, whatever `noIndex` says.
|
|
240
|
+
|
|
237
241
|
### Example
|
|
238
242
|
|
|
239
243
|
```http
|
package/docs/8.filters.md
CHANGED
|
@@ -150,9 +150,11 @@ type FilterFunction<T, U> = (
|
|
|
150
150
|
context: RequestContext & { this: T },
|
|
151
151
|
proxy: ValueProxy<any>,
|
|
152
152
|
key: string,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
...rest: [
|
|
154
|
+
value: string,
|
|
155
|
+
mode: "eq" | "ne" | "gt" | "ge" | "lt" | "le",
|
|
156
|
+
row: ValueProxy<U>,
|
|
157
|
+
]
|
|
156
158
|
) => ValueProxyOrValue<boolean>;
|
|
157
159
|
```
|
|
158
160
|
|
|
@@ -165,6 +167,8 @@ type FilterFunction<T, U> = (
|
|
|
165
167
|
| `mode` | The comparison operator from the query parameter |
|
|
166
168
|
| `row` | The full database row as a `ValueProxy` |
|
|
167
169
|
|
|
170
|
+
`value`, `mode` and `row` are one rest tuple, so that the function type stays within five parameters. A function that declares them as separate parameters is assignable to it, and `Parameters<FilterFunction<T, U>>` lists all six.
|
|
171
|
+
|
|
168
172
|
### Example: Pattern Matching Filter
|
|
169
173
|
|
|
170
174
|
```typescript
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/interface-data-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "AntelopeJS interface that generates CRUD REST endpoints from database tables, with access rules, validation and pagination",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelopejs",
|
|
@@ -60,16 +60,26 @@
|
|
|
60
60
|
"release": "pnpm run lint && pnpm run prepack && release-it",
|
|
61
61
|
"test": "pnpm run build && ajs module test .",
|
|
62
62
|
"format:check": "oxfmt --check .",
|
|
63
|
-
"knip": "knip"
|
|
63
|
+
"knip": "knip",
|
|
64
|
+
"prepare": "if git rev-parse --is-inside-work-tree >/dev/null 2>&1 && command -v lefthook >/dev/null 2>&1; then lefthook install; fi",
|
|
65
|
+
"checks": "antelopejs-checks",
|
|
66
|
+
"size": "antelopejs-check-size",
|
|
67
|
+
"ratchet": "antelopejs-ratchet",
|
|
68
|
+
"sync": "antelopejs-sync",
|
|
69
|
+
"ratchet:update": "antelopejs-ratchet --update",
|
|
70
|
+
"sync:check": "antelopejs-sync --check",
|
|
71
|
+
"typecheck": "tsc --noEmit",
|
|
72
|
+
"suppressions": "antelopejs-suppressions",
|
|
73
|
+
"suppressions:update": "antelopejs-suppressions --update"
|
|
64
74
|
},
|
|
65
75
|
"devDependencies": {
|
|
66
76
|
"@antelopejs/core": "^1.16.0",
|
|
67
77
|
"@antelopejs/interface-api": "^0.0.15",
|
|
68
78
|
"@antelopejs/interface-api-util": "^0.1.3",
|
|
69
79
|
"@antelopejs/interface-core": "^0.2.4",
|
|
70
|
-
"@antelopejs/interface-database": "^0.1.
|
|
71
|
-
"@antelopejs/interface-database-decorators": "^0.1.
|
|
72
|
-
"@antelopejs/tooling-configs": "^0.
|
|
80
|
+
"@antelopejs/interface-database": "^0.1.11",
|
|
81
|
+
"@antelopejs/interface-database-decorators": "^0.1.10",
|
|
82
|
+
"@antelopejs/tooling-configs": "^0.2.11",
|
|
73
83
|
"@types/chai": "^4.3.20",
|
|
74
84
|
"@types/mocha": "^10.0.10",
|
|
75
85
|
"@types/node": "^22.19.15",
|
|
@@ -77,6 +87,7 @@
|
|
|
77
87
|
"chai": "^4.5.0",
|
|
78
88
|
"eslint-plugin-perfectionist": "^5.11.0",
|
|
79
89
|
"knip": "^6.34.0",
|
|
90
|
+
"lefthook": "^2.2.2",
|
|
80
91
|
"mongodb-memory-server-core": "^10.4.3",
|
|
81
92
|
"oxfmt": "^0.66.0",
|
|
82
93
|
"oxlint": "1.81.0",
|
|
@@ -91,7 +102,7 @@
|
|
|
91
102
|
"@antelopejs/interface-api-util": ">=0.1.3 <1.0.0",
|
|
92
103
|
"@antelopejs/interface-core": ">=0.2.4 <1.0.0",
|
|
93
104
|
"@antelopejs/interface-database": ">=0.1.10 <1.0.0",
|
|
94
|
-
"@antelopejs/interface-database-decorators": ">=0.1.
|
|
105
|
+
"@antelopejs/interface-database-decorators": ">=0.1.10 <1.0.0"
|
|
95
106
|
},
|
|
96
107
|
"packageManager": "pnpm@10.6.5",
|
|
97
108
|
"antelopeJs": {
|
|
@@ -140,6 +140,10 @@ This exposes `GET /tasks/get?id=`, `GET /tasks/list`, `POST /tasks/new`, `PUT /t
|
|
|
140
140
|
- `edit` is a partial update: a field absent from the body is left unchanged, `null` clears it, and
|
|
141
141
|
setters only run for the fields present. `@Mandatory("edit")` rejects a field cleared by the body
|
|
142
142
|
(`null`, `""`, `[]`), not an absent one. `new` still writes every writable field and runs every setter.
|
|
143
|
+
- A parsed request body is `FieldValues` (fields by name; a `FieldValue` is JSON or a `Date`). A
|
|
144
|
+
`@Validator` receives `FieldValue | undefined`, and `Validation.ParseBody` answers 400 for a body that
|
|
145
|
+
holds anything else, even one a wrapping route already parsed. The default routes run with the
|
|
146
|
+
controller as `this` (`DataControllerInstance`); a wrapper calls `route.func.call(this, ...)`.
|
|
143
147
|
- A `@Validator` may return a parse result `{ success, data }` (e.g. zod's `safeParseAsync(value)`)
|
|
144
148
|
instead of a boolean: `data` is then written in place of the raw value.
|
|
145
149
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"cases": [
|
|
4
|
+
{
|
|
5
|
+
"id": "data-api-interface-crud-endpoints",
|
|
6
|
+
"area": "framework",
|
|
7
|
+
"style": "clean",
|
|
8
|
+
"prompt": "Expose my orders table as REST endpoints for get, list, create, update and delete. Only the status column should be sortable and customer filterable.",
|
|
9
|
+
"expect": {
|
|
10
|
+
"skills": ["data-api-interface"],
|
|
11
|
+
"acceptable": [
|
|
12
|
+
"api-interface",
|
|
13
|
+
"database-decorators-interface",
|
|
14
|
+
"database-interface",
|
|
15
|
+
"ajs-consume-interface",
|
|
16
|
+
"antelopejs"
|
|
17
|
+
],
|
|
18
|
+
"mustMention": [
|
|
19
|
+
"DataController",
|
|
20
|
+
"RegisterDataController",
|
|
21
|
+
"Sortable",
|
|
22
|
+
"Filter"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "data-api-interface-crud-casual",
|
|
28
|
+
"area": "framework",
|
|
29
|
+
"style": "casual",
|
|
30
|
+
"prompt": "can i get a crud api straight from my table without writing every route",
|
|
31
|
+
"expect": {
|
|
32
|
+
"skills": ["data-api-interface"],
|
|
33
|
+
"acceptable": [
|
|
34
|
+
"api-interface",
|
|
35
|
+
"database-decorators-interface",
|
|
36
|
+
"database-interface",
|
|
37
|
+
"antelopejs"
|
|
38
|
+
],
|
|
39
|
+
"mustMention": ["DataController", "DefaultRoutes"]
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "data-api-interface-field-hidden",
|
|
44
|
+
"area": "framework",
|
|
45
|
+
"style": "clean",
|
|
46
|
+
"prompt": "The list endpoint I generated from my table never returns the email field and I cannot write to it either. Why?",
|
|
47
|
+
"expect": {
|
|
48
|
+
"skills": ["data-api-interface"],
|
|
49
|
+
"acceptable": [
|
|
50
|
+
"api-interface",
|
|
51
|
+
"database-decorators-interface",
|
|
52
|
+
"ajs-consume-interface"
|
|
53
|
+
],
|
|
54
|
+
"mustMention": ["Access"]
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "data-api-interface-sort-400-casual",
|
|
59
|
+
"area": "framework",
|
|
60
|
+
"style": "casual",
|
|
61
|
+
"prompt": "sorting the list by createdAt gives me a 400",
|
|
62
|
+
"expect": {
|
|
63
|
+
"skills": ["data-api-interface"],
|
|
64
|
+
"acceptable": ["api-interface", "database-decorators-interface"],
|
|
65
|
+
"mustMention": ["Sortable"]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "data-api-interface-table-neighbour",
|
|
70
|
+
"area": "framework",
|
|
71
|
+
"style": "clean",
|
|
72
|
+
"prompt": "Add a hashed passphrase and an encrypted supplier code to my chef table class, plus created and updated timestamps.",
|
|
73
|
+
"expect": {
|
|
74
|
+
"skills": ["database-decorators-interface"],
|
|
75
|
+
"acceptable": [
|
|
76
|
+
"database-interface",
|
|
77
|
+
"antelopejs",
|
|
78
|
+
"ajs-consume-interface"
|
|
79
|
+
],
|
|
80
|
+
"avoid": ["data-api-interface"]
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "data-api-interface-wrap-new-stamp",
|
|
85
|
+
"area": "framework",
|
|
86
|
+
"style": "clean",
|
|
87
|
+
"prompt": "I want the generated create route to stamp a source field on every body before it is validated and written. How do I wrap it, and what type does the parsed body have?",
|
|
88
|
+
"expect": {
|
|
89
|
+
"skills": ["data-api-interface"],
|
|
90
|
+
"acceptable": [
|
|
91
|
+
"api-interface",
|
|
92
|
+
"database-decorators-interface",
|
|
93
|
+
"ajs-consume-interface"
|
|
94
|
+
],
|
|
95
|
+
"mustMention": ["ParseBody", "WrittenBody", "FieldValues"]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
}
|