@antelopejs/interface-data-api 0.1.10 → 0.2.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/dist/components.d.ts +37 -6
- package/dist/components.js +99 -21
- package/dist/components.js.map +1 -1
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/metadata.d.ts +23 -3
- package/dist/metadata.js +1 -3
- package/dist/metadata.js.map +1 -1
- package/dist/tests/components/mandatory.test.js +27 -6
- package/dist/tests/components/mandatory.test.js.map +1 -1
- package/dist/tests/components/parsed_value.test.d.ts +1 -0
- package/dist/tests/components/parsed_value.test.js +152 -0
- package/dist/tests/components/parsed_value.test.js.map +1 -0
- package/dist/tests/components/partial_edit.test.d.ts +1 -0
- package/dist/tests/components/partial_edit.test.js +277 -0
- package/dist/tests/components/partial_edit.test.js.map +1 -0
- package/dist/tests/index/inheritance.test.d.ts +1 -0
- package/dist/tests/index/inheritance.test.js +122 -0
- package/dist/tests/index/inheritance.test.js.map +1 -0
- package/docs/2.data-controllers.md +1 -1
- package/docs/3.routes.md +13 -1
- package/docs/5.validators.md +29 -8
- package/package.json +1 -1
- package/skills/data-api-interface/SKILL.md +7 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const chai_1 = require("chai");
|
|
13
|
+
const interface_api_1 = require("@antelopejs/interface-api");
|
|
14
|
+
const metadata_1 = require("@antelopejs/interface-data-api/metadata");
|
|
15
|
+
const interface_data_api_1 = require("@antelopejs/interface-data-api");
|
|
16
|
+
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
17
|
+
const utils_1 = require("../utils");
|
|
18
|
+
const NOTE_TABLE = "inheritance-notes";
|
|
19
|
+
const ARCHIVE_TABLE = "inheritance-archives";
|
|
20
|
+
const SCHEMA = "default";
|
|
21
|
+
const DERIVED_LOCATION = "inheritance-derived";
|
|
22
|
+
const OVERRIDE_LOCATION = "inheritance-override";
|
|
23
|
+
const OK = 200;
|
|
24
|
+
const NOT_FOUND = 404;
|
|
25
|
+
let Note = class Note extends interface_database_decorators_1.Table {
|
|
26
|
+
};
|
|
27
|
+
__decorate([
|
|
28
|
+
(0, interface_database_decorators_1.Field)("string"),
|
|
29
|
+
__metadata("design:type", String)
|
|
30
|
+
], Note.prototype, "title", void 0);
|
|
31
|
+
Note = __decorate([
|
|
32
|
+
(0, interface_database_decorators_1.RegisterTable)(NOTE_TABLE, SCHEMA)
|
|
33
|
+
], Note);
|
|
34
|
+
class NoteModel extends (0, interface_database_decorators_1.BasicDataModel)(Note, NOTE_TABLE) {
|
|
35
|
+
}
|
|
36
|
+
let Archive = class Archive extends interface_database_decorators_1.Table {
|
|
37
|
+
};
|
|
38
|
+
__decorate([
|
|
39
|
+
(0, interface_database_decorators_1.Field)("string"),
|
|
40
|
+
__metadata("design:type", String)
|
|
41
|
+
], Archive.prototype, "title", void 0);
|
|
42
|
+
Archive = __decorate([
|
|
43
|
+
(0, interface_database_decorators_1.RegisterTable)(ARCHIVE_TABLE, SCHEMA)
|
|
44
|
+
], Archive);
|
|
45
|
+
class ArchiveModel extends (0, interface_database_decorators_1.BasicDataModel)(Archive, ARCHIVE_TABLE) {
|
|
46
|
+
}
|
|
47
|
+
class NoteBaseAPI extends (0, interface_data_api_1.DataController)(Note, {}, (0, interface_api_1.Controller)("/inheritance-base")) {
|
|
48
|
+
}
|
|
49
|
+
__decorate([
|
|
50
|
+
(0, metadata_1.ModelReference)(),
|
|
51
|
+
(0, interface_database_decorators_1.Model)(NoteModel),
|
|
52
|
+
__metadata("design:type", NoteModel)
|
|
53
|
+
], NoteBaseAPI.prototype, "model", void 0);
|
|
54
|
+
__decorate([
|
|
55
|
+
(0, metadata_1.Listable)(),
|
|
56
|
+
(0, metadata_1.Access)(metadata_1.AccessMode.ReadOnly),
|
|
57
|
+
__metadata("design:type", String)
|
|
58
|
+
], NoteBaseAPI.prototype, "_id", void 0);
|
|
59
|
+
__decorate([
|
|
60
|
+
(0, metadata_1.Listable)(),
|
|
61
|
+
(0, metadata_1.Access)(metadata_1.AccessMode.ReadWrite),
|
|
62
|
+
__metadata("design:type", String)
|
|
63
|
+
], NoteBaseAPI.prototype, "title", void 0);
|
|
64
|
+
let _DerivedNoteAPI = class _DerivedNoteAPI extends (0, interface_data_api_1.DataController)(Note, interface_data_api_1.DefaultRoutes.All, (0, interface_api_1.Controller)(`/${DERIVED_LOCATION}`, NoteBaseAPI)) {
|
|
65
|
+
};
|
|
66
|
+
_DerivedNoteAPI = __decorate([
|
|
67
|
+
(0, interface_data_api_1.RegisterDataController)()
|
|
68
|
+
], _DerivedNoteAPI);
|
|
69
|
+
let _ArchiveAPI = class _ArchiveAPI extends (0, interface_data_api_1.DataController)(Archive, { get: interface_data_api_1.DefaultRoutes.Get }, (0, interface_api_1.Controller)(`/${OVERRIDE_LOCATION}`, NoteBaseAPI)) {
|
|
70
|
+
};
|
|
71
|
+
__decorate([
|
|
72
|
+
(0, metadata_1.ModelReference)(),
|
|
73
|
+
(0, interface_database_decorators_1.Model)(ArchiveModel),
|
|
74
|
+
__metadata("design:type", ArchiveModel)
|
|
75
|
+
], _ArchiveAPI.prototype, "archiveModel", void 0);
|
|
76
|
+
_ArchiveAPI = __decorate([
|
|
77
|
+
(0, interface_data_api_1.RegisterDataController)()
|
|
78
|
+
], _ArchiveAPI);
|
|
79
|
+
describe("Derived data controllers", () => {
|
|
80
|
+
let noteModel;
|
|
81
|
+
let archiveModel;
|
|
82
|
+
before(async () => {
|
|
83
|
+
await (0, interface_database_decorators_1.RegisterSchema)(SCHEMA);
|
|
84
|
+
noteModel = new NoteModel((0, utils_1.getSchemaInstance)(SCHEMA));
|
|
85
|
+
archiveModel = new ArchiveModel((0, utils_1.getSchemaInstance)(SCHEMA));
|
|
86
|
+
});
|
|
87
|
+
it("inherits the parent model reference for get and list", async () => {
|
|
88
|
+
const [id] = await noteModel.insert({ title: "Inherited" });
|
|
89
|
+
const get = await (0, utils_1.getRequest)(DERIVED_LOCATION, { id });
|
|
90
|
+
(0, chai_1.expect)(get.status).to.equal(OK);
|
|
91
|
+
(0, chai_1.expect)(await get.json()).to.deep.equal({ _id: id, title: "Inherited" });
|
|
92
|
+
const list = await (0, utils_1.listRequest)(DERIVED_LOCATION);
|
|
93
|
+
(0, chai_1.expect)(list.status).to.equal(OK);
|
|
94
|
+
const { results } = (await list.json());
|
|
95
|
+
(0, chai_1.expect)(results).to.deep.include({ _id: id, title: "Inherited" });
|
|
96
|
+
});
|
|
97
|
+
it("inherits the parent model reference for new, edit and delete", async () => {
|
|
98
|
+
const created = await (0, utils_1.newRequest)(DERIVED_LOCATION, { title: "Created" });
|
|
99
|
+
(0, chai_1.expect)(created.status).to.equal(OK);
|
|
100
|
+
const [id] = (await created.json());
|
|
101
|
+
(0, chai_1.expect)(await noteModel.get(id)).to.have.property("title", "Created");
|
|
102
|
+
const edited = await (0, utils_1.editRequest)(DERIVED_LOCATION, { title: "Edited" }, { id });
|
|
103
|
+
(0, chai_1.expect)(edited.status).to.equal(OK);
|
|
104
|
+
(0, chai_1.expect)(await noteModel.get(id)).to.have.property("title", "Edited");
|
|
105
|
+
const deleted = await (0, utils_1.deleteRequest)(DERIVED_LOCATION, { id });
|
|
106
|
+
(0, chai_1.expect)(deleted.status).to.equal(OK);
|
|
107
|
+
(0, chai_1.expect)(await noteModel.get(id)).to.equal(undefined);
|
|
108
|
+
});
|
|
109
|
+
it("prefers the child's own model reference over the parent's", async () => {
|
|
110
|
+
const [archiveId] = await archiveModel.insert({ title: "Archived" });
|
|
111
|
+
const [noteId] = await noteModel.insert({ title: "Note" });
|
|
112
|
+
const archive = await (0, utils_1.getRequest)(OVERRIDE_LOCATION, { id: archiveId });
|
|
113
|
+
(0, chai_1.expect)(archive.status).to.equal(OK);
|
|
114
|
+
(0, chai_1.expect)(await archive.json()).to.deep.equal({
|
|
115
|
+
_id: archiveId,
|
|
116
|
+
title: "Archived",
|
|
117
|
+
});
|
|
118
|
+
const note = await (0, utils_1.getRequest)(OVERRIDE_LOCATION, { id: noteId });
|
|
119
|
+
(0, chai_1.expect)(note.status).to.equal(NOT_FOUND);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
//# sourceMappingURL=inheritance.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inheritance.test.js","sourceRoot":"","sources":["../../../src/tests/index/inheritance.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -94,7 +94,7 @@ Every data controller requires the following elements:
|
|
|
94
94
|
1. **Table Class** - A database table decorated with `@RegisterTable`, defining the schema.
|
|
95
95
|
2. **Route Definitions** - Which operations to expose. Use `DefaultRoutes.All` or a custom selection.
|
|
96
96
|
3. **Controller Base** - The URL path prefix, created with `Controller("/path")`.
|
|
97
|
-
4. **Model Reference** - A property decorated with `@ModelReference()` that holds a database model instance. This model provides the actual database operations (insert, get, update, delete).
|
|
97
|
+
4. **Model Reference** - A property decorated with `@ModelReference()` that holds a database model instance. This model provides the actual database operations (insert, get, update, delete). A controller derived from another data controller (for example `Controller("/path", ParentController)`) inherits its model reference unless it declares its own.
|
|
98
98
|
|
|
99
99
|
## The `@RegisterDataController` Decorator
|
|
100
100
|
|
package/docs/3.routes.md
CHANGED
|
@@ -144,6 +144,8 @@ Content-Type: application/json
|
|
|
144
144
|
["user-789"]
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
+
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`.
|
|
148
|
+
|
|
147
149
|
Before inserting, the `insert` event is triggered on any table modifiers attached to the database table.
|
|
148
150
|
|
|
149
151
|
### Edit
|
|
@@ -158,7 +160,17 @@ Updates an existing record by its identifier.
|
|
|
158
160
|
| --------- | -------- | ----------------- |
|
|
159
161
|
| `id` | Yes | Record identifier |
|
|
160
162
|
|
|
161
|
-
**Body:** JSON object with fields to update.
|
|
163
|
+
**Body:** JSON object with the fields to update. Only the fields present in the body are written:
|
|
164
|
+
|
|
165
|
+
| In the body | Effect on the field |
|
|
166
|
+
| ----------- | ------------------- |
|
|
167
|
+
| key absent | unchanged |
|
|
168
|
+
| `null` | cleared |
|
|
169
|
+
| a value | set |
|
|
170
|
+
|
|
171
|
+
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.
|
|
172
|
+
|
|
173
|
+
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`.
|
|
162
174
|
|
|
163
175
|
**Example:**
|
|
164
176
|
|
package/docs/5.validators.md
CHANGED
|
@@ -8,7 +8,7 @@ Validators provide field-level data integrity checks that run automatically duri
|
|
|
8
8
|
|
|
9
9
|
### Basic Usage
|
|
10
10
|
|
|
11
|
-
The `@Validator` decorator accepts a function that receives the field value and returns `true` if valid or `false` if invalid. The function can also return a `Promise
|
|
11
|
+
The `@Validator` decorator accepts a function that receives the field value and returns `true` if valid or `false` if invalid. It can instead return a parse result (see [Parsed Values](#parsed-values)) so that the parsed value is written. The function can also return a `Promise` of either for asynchronous validation.
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { Validator } from "@antelopejs/interface-data-api/metadata";
|
|
@@ -36,16 +36,34 @@ class UserAPI extends DataController(
|
|
|
36
36
|
### Validation Function Signature
|
|
37
37
|
|
|
38
38
|
```typescript
|
|
39
|
-
type
|
|
39
|
+
type FieldValidator = (
|
|
40
|
+
value: unknown,
|
|
41
|
+
) => FieldValidationResult | Promise<FieldValidationResult>;
|
|
42
|
+
|
|
43
|
+
type FieldValidationResult =
|
|
44
|
+
boolean | { success: true; data: unknown } | { success: false };
|
|
40
45
|
```
|
|
41
46
|
|
|
42
47
|
The function:
|
|
43
48
|
|
|
44
49
|
- Receives the raw field value from the request body.
|
|
45
|
-
- Returns `true` when the value is valid.
|
|
46
|
-
- Returns `
|
|
50
|
+
- Returns `true` when the value is valid: the raw value is written.
|
|
51
|
+
- Returns `{ success: true, data }` when the value is valid: `data` is written instead of the raw value.
|
|
52
|
+
- Returns `false` or `{ success: false }` when the value is invalid, which triggers a `400 Bad Request` response.
|
|
47
53
|
- Should handle unexpected types gracefully (e.g., check `typeof` before accessing properties).
|
|
48
54
|
|
|
55
|
+
### Parsed Values
|
|
56
|
+
|
|
57
|
+
A validator returning a parse result writes the parsed value on both `new` and `edit`. The result has the shape of zod's `safeParse`, so a zod schema can be used directly:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const startsAtSchema = z.coerce.date();
|
|
61
|
+
|
|
62
|
+
@Access(AccessMode.ReadWrite)
|
|
63
|
+
@Validator((value) => startsAtSchema.safeParseAsync(value))
|
|
64
|
+
declare startsAt: Date; // "2026-01-15" is stored as a Date
|
|
65
|
+
```
|
|
66
|
+
|
|
49
67
|
## Common Validation Patterns
|
|
50
68
|
|
|
51
69
|
### String Validation
|
|
@@ -112,7 +130,10 @@ declare email: string;
|
|
|
112
130
|
|
|
113
131
|
## The `@Mandatory` Decorator
|
|
114
132
|
|
|
115
|
-
The `@Mandatory` decorator specifies which operations require a field
|
|
133
|
+
The `@Mandatory` decorator specifies which operations require a field in the request body. Unlike `@Validator`, which checks field content, `@Mandatory` only verifies that the field is provided:
|
|
134
|
+
|
|
135
|
+
- On `"edit"`, the body only carries the fields to update: a mandatory field absent from the body keeps its stored value and is accepted, while a mandatory field cleared by the body (`null`, `""` or `[]`) is rejected.
|
|
136
|
+
- On any other operation (such as `"new"`), a mandatory field must be present in the body.
|
|
116
137
|
|
|
117
138
|
```typescript
|
|
118
139
|
import { Mandatory } from "@antelopejs/interface-data-api/metadata";
|
|
@@ -152,9 +173,9 @@ class UserAPI extends DataController(
|
|
|
152
173
|
|
|
153
174
|
In this example:
|
|
154
175
|
|
|
155
|
-
- `email` must be provided
|
|
176
|
+
- `email` must be provided when creating a record and cannot be cleared when editing one.
|
|
156
177
|
- `password` is required only when creating a new record.
|
|
157
|
-
- `status`
|
|
178
|
+
- `status` cannot be cleared when editing an existing record.
|
|
158
179
|
- `notes` is always optional.
|
|
159
180
|
|
|
160
181
|
### Skip Mandatory Validation
|
|
@@ -177,7 +198,7 @@ class UserAPI extends DataController(User, routes, Controller("/users")) {
|
|
|
177
198
|
}
|
|
178
199
|
```
|
|
179
200
|
|
|
180
|
-
Requests to `/users/edit`
|
|
201
|
+
Requests to `/users/edit` cannot clear `status`, while requests to `/users/patchEdit` can.
|
|
181
202
|
|
|
182
203
|
## The `@Optional` Decorator
|
|
183
204
|
|
package/package.json
CHANGED
|
@@ -111,7 +111,8 @@ This exposes `GET /tasks/get?id=`, `GET /tasks/list`, `POST /tasks/new`, `PUT /t
|
|
|
111
111
|
|
|
112
112
|
- `@RegisterDataController()` is mandatory: it wires field decorators, parameter providers, and
|
|
113
113
|
routes. A `@ModelReference()` property holding a `@Model` instance is required too (`Query.GetModel`
|
|
114
|
-
throws 500 without it).
|
|
114
|
+
throws 500 without it). A controller derived from another data controller inherits it unless it
|
|
115
|
+
declares its own.
|
|
115
116
|
- Fields with no decorator are invisible to the API — neither returned nor writable; visibility
|
|
116
117
|
requires `@Access`. `@Optional()` only registers a field as not-mandatory (it does not expose it).
|
|
117
118
|
- `list` responds `{ results, total, offset, limit }`. `limit` defaults to 10 and is capped at 100
|
|
@@ -127,6 +128,11 @@ This exposes `GET /tasks/get?id=`, `GET /tasks/list`, `POST /tasks/new`, `PUT /t
|
|
|
127
128
|
of `DefaultRoutes.All`; use `DefaultRoutes.WithOptions(route, options, endpoint)` to rename an
|
|
128
129
|
endpoint or preset parameter options.
|
|
129
130
|
- `@Access` accepts per-action overrides, e.g. `@Access(AccessMode.ReadOnly, { edit: AccessMode.ReadWrite })`.
|
|
131
|
+
- `edit` is a partial update: a field absent from the body is left unchanged, `null` clears it, and
|
|
132
|
+
setters only run for the fields present. `@Mandatory("edit")` rejects a field cleared by the body
|
|
133
|
+
(`null`, `""`, `[]`), not an absent one. `new` still writes every writable field and runs every setter.
|
|
134
|
+
- A `@Validator` may return a parse result `{ success, data }` (e.g. zod's `safeParseAsync(value)`)
|
|
135
|
+
instead of a boolean: `data` is then written in place of the raw value.
|
|
130
136
|
|
|
131
137
|
## Deeper reference
|
|
132
138
|
|