@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.
@@ -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
 
@@ -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<boolean>` for asynchronous validation.
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 ValidatorFunction = (value: unknown) => boolean | Promise<boolean>;
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 `false` when the value is invalid, which triggers a `400 Bad Request` response.
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 to be present in the request body. Unlike `@Validator`, which checks field content, `@Mandatory` only verifies that the field exists in the payload.
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 in both create and update requests.
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` is required only when editing an existing record.
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` require `status`, while requests to `/users/patchEdit` do not.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/interface-data-api",
3
- "version": "0.1.10",
3
+ "version": "0.2.0",
4
4
  "description": "AntelopeJS interface for data API with CRUD controllers and route generation",
5
5
  "keywords": [
6
6
  "antelopejs",
@@ -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