@antelopejs/interface-data-api 0.1.6 → 0.1.7

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,320 @@
1
+ # Routes
2
+
3
+ ## Overview
4
+
5
+ Routes define which operations are available on a data controller. The module provides predefined route configurations that automatically generate endpoints with the correct HTTP methods, parameter extraction, and response handling.
6
+
7
+ ## Default Routes
8
+
9
+ The `DefaultRoutes` namespace provides five built-in routes covering standard CRUD operations.
10
+
11
+ ```typescript
12
+ import { DefaultRoutes } from "@antelopejs/interface-data-api";
13
+ ```
14
+
15
+ | Route | Object | HTTP Method | Endpoint Pattern |
16
+ | ----- | ------ | ----------- | ---------------- |
17
+ | Get | `DefaultRoutes.Get` | GET | `/resource/get?id=<id>` |
18
+ | List | `DefaultRoutes.List` | GET | `/resource/list` |
19
+ | New | `DefaultRoutes.New` | POST | `/resource/new` |
20
+ | Edit | `DefaultRoutes.Edit` | PUT | `/resource/edit?id=<id>` |
21
+ | Delete | `DefaultRoutes.Delete` | DELETE | `/resource/delete?id=<id>` |
22
+
23
+ Use `DefaultRoutes.All` to include all five routes at once.
24
+
25
+ ## Route Configuration
26
+
27
+ Pass a route definition object to `DataController` to control which endpoints are created.
28
+
29
+ ```typescript
30
+ // All CRUD routes
31
+ @RegisterDataController()
32
+ class UserAPI extends DataController(
33
+ User,
34
+ DefaultRoutes.All,
35
+ Controller("/users"),
36
+ ) {
37
+ // ...
38
+ }
39
+
40
+ // Only specific routes
41
+ @RegisterDataController()
42
+ class UserAPI extends DataController(
43
+ User,
44
+ {
45
+ get: DefaultRoutes.Get,
46
+ list: DefaultRoutes.List,
47
+ new: DefaultRoutes.New,
48
+ },
49
+ Controller("/users"),
50
+ ) {
51
+ // edit and delete endpoints are not created
52
+ }
53
+ ```
54
+
55
+ ## Route Details
56
+
57
+ ### Get
58
+
59
+ Retrieves a single record by its identifier.
60
+
61
+ **Endpoint:** `GET /resource/get`
62
+
63
+ **Query Parameters:**
64
+
65
+ | Parameter | Required | Description |
66
+ | --------- | -------- | ----------- |
67
+ | `id` | Yes | Record identifier |
68
+
69
+ **Example:**
70
+
71
+ ```http
72
+ GET /users/get?id=user-123
73
+ ```
74
+
75
+ **Response:**
76
+
77
+ ```json
78
+ {
79
+ "_id": "user-123",
80
+ "name": "Bob",
81
+ "email": "bob@example.com"
82
+ }
83
+ ```
84
+
85
+ ### List
86
+
87
+ Retrieves multiple records with pagination, sorting, and filtering support.
88
+
89
+ **Endpoint:** `GET /resource/list`
90
+
91
+ **Query Parameters:**
92
+
93
+ | Parameter | Required | Default | Description |
94
+ | --------- | -------- | ------- | ----------- |
95
+ | `limit` | No | `10` | Maximum number of records to return |
96
+ | `offset` | No | `0` | Number of records to skip |
97
+ | `sortKey` | No | - | Field to sort by (must be marked `@Sortable`) |
98
+ | `sortDirection` | No | `asc` | Sort direction: `asc` or `desc` |
99
+
100
+ **Example:**
101
+
102
+ ```http
103
+ GET /users/list?limit=10&offset=0&sortKey=name&sortDirection=asc
104
+ ```
105
+
106
+ **Response:**
107
+
108
+ ```json
109
+ {
110
+ "results": [
111
+ { "_id": "user-123", "name": "Alice", "email": "alice@example.com" },
112
+ { "_id": "user-456", "name": "Bob", "email": "bob@example.com" }
113
+ ],
114
+ "total": 42,
115
+ "offset": 0,
116
+ "limit": 10
117
+ }
118
+ ```
119
+
120
+ ### New
121
+
122
+ Creates a new record. The request body must be a JSON object with the fields to set.
123
+
124
+ **Endpoint:** `POST /resource/new`
125
+
126
+ **Body:** JSON object with field values.
127
+
128
+ **Example:**
129
+
130
+ ```http
131
+ POST /users/new
132
+ Content-Type: application/json
133
+
134
+ {
135
+ "name": "Bob",
136
+ "email": "bob@example.com",
137
+ "password": "secure123"
138
+ }
139
+ ```
140
+
141
+ **Response:**
142
+
143
+ ```json
144
+ ["user-789"]
145
+ ```
146
+
147
+ Before inserting, the `insert` event is triggered on any table modifiers attached to the database table.
148
+
149
+ ### Edit
150
+
151
+ Updates an existing record by its identifier.
152
+
153
+ **Endpoint:** `PUT /resource/edit`
154
+
155
+ **Query Parameters:**
156
+
157
+ | Parameter | Required | Description |
158
+ | --------- | -------- | ----------- |
159
+ | `id` | Yes | Record identifier |
160
+
161
+ **Body:** JSON object with fields to update.
162
+
163
+ **Example:**
164
+
165
+ ```http
166
+ PUT /users/edit?id=user-123
167
+ Content-Type: application/json
168
+
169
+ {
170
+ "name": "Robert",
171
+ "email": "robert@example.com"
172
+ }
173
+ ```
174
+
175
+ **Response:**
176
+
177
+ ```http
178
+ 200 OK
179
+ ```
180
+
181
+ Before updating, the `update` event is triggered on any table modifiers attached to the database table.
182
+
183
+ ### Delete
184
+
185
+ Deletes one or more records by their identifiers.
186
+
187
+ **Endpoint:** `DELETE /resource/delete`
188
+
189
+ **Query Parameters:**
190
+
191
+ | Parameter | Required | Description |
192
+ | --------- | -------- | ----------- |
193
+ | `id` | Yes | Record identifier(s). Pass multiple `id` parameters to delete several records at once. |
194
+
195
+ **Example:**
196
+
197
+ ```http
198
+ DELETE /users/delete?id=user-123
199
+ ```
200
+
201
+ **Response:**
202
+
203
+ ```http
204
+ 200 OK
205
+ ```
206
+
207
+ ## Route Options with `WithOptions`
208
+
209
+ The `DefaultRoutes.WithOptions` function customizes route behavior by overriding query parameters or route settings.
210
+
211
+ ```typescript
212
+ DefaultRoutes.WithOptions(callback, options?, endpoint?)
213
+ ```
214
+
215
+ | Parameter | Description |
216
+ | --------- | ----------- |
217
+ | `callback` | The route callback to customize (e.g., `DefaultRoutes.Edit`). |
218
+ | `options` | An object with option overrides. These values take precedence over query parameters. |
219
+ | `endpoint` | An optional custom endpoint path. Defaults to the route key name. |
220
+
221
+ ### Example
222
+
223
+ ```typescript
224
+ const routes = {
225
+ edit: DefaultRoutes.Edit,
226
+ quickEdit: DefaultRoutes.WithOptions(DefaultRoutes.Edit, {
227
+ noMandatory: "true",
228
+ }),
229
+ };
230
+
231
+ @RegisterDataController()
232
+ class UserAPI extends DataController(User, routes, Controller("/users")) {
233
+ // /users/edit enforces mandatory fields
234
+ // /users/quickEdit skips mandatory field validation
235
+ }
236
+ ```
237
+
238
+ ### Available Options per Route
239
+
240
+ **Get:**
241
+
242
+ | Option | Type | Description |
243
+ | ------ | ---- | ----------- |
244
+ | `index` | `string` | Use a secondary index instead of the primary key |
245
+ | `noForeign` | `string` | Skip foreign key resolution |
246
+
247
+ **List:**
248
+
249
+ | Option | Type | Description |
250
+ | ------ | ---- | ----------- |
251
+ | `maxPage` | `number` | Maximum allowed value for `limit` (caps the page size) |
252
+ | `noForeign` | `boolean` | Skip foreign key resolution |
253
+ | `noPluck` | `boolean` | Return all database fields instead of plucked fields |
254
+ | `pluckMode` | `string` | Select a named pluck mode (see `@Listable`) |
255
+
256
+ **New:**
257
+
258
+ | Option | Type | Description |
259
+ | ------ | ---- | ----------- |
260
+ | `noMandatory` | `string` | Skip mandatory field validation |
261
+
262
+ **Edit:**
263
+
264
+ | Option | Type | Description |
265
+ | ------ | ---- | ----------- |
266
+ | `index` | `string` | Use a secondary index instead of the primary key |
267
+ | `noMandatory` | `string` | Skip mandatory field validation |
268
+
269
+ ## Custom Routes
270
+
271
+ For operations beyond CRUD, define a custom route callback with `func`, `args`, and `method` properties.
272
+
273
+ ```typescript
274
+ import { Context } from "@antelopejs/interface-api";
275
+ import {
276
+ DataController,
277
+ DefaultRoutes,
278
+ RegisterDataController,
279
+ } from "@antelopejs/interface-data-api";
280
+ import { Parameters } from "@antelopejs/interface-data-api/components";
281
+
282
+ const Search = {
283
+ func: async (ctx, params) => {
284
+ // Custom search logic
285
+ return { results: [] };
286
+ },
287
+ args: [Context(), Parameters.List()],
288
+ method: "get",
289
+ };
290
+
291
+ @RegisterDataController()
292
+ class UserAPI extends DataController(
293
+ User,
294
+ {
295
+ ...DefaultRoutes.All,
296
+ search: Search,
297
+ },
298
+ Controller("/users"),
299
+ ) {
300
+ // All default routes plus a custom /users/search endpoint
301
+ }
302
+ ```
303
+
304
+ The `args` array contains parameter decorators that extract values from the request context. Each decorator corresponds to a parameter in the `func` callback, in order.
305
+
306
+ ## Error Handling
307
+
308
+ Default routes return standard HTTP error responses:
309
+
310
+ | Status | Condition |
311
+ | ------ | --------- |
312
+ | `400 Bad Request` | Validation fails, missing required fields, or invalid parameters |
313
+ | `404 Not Found` | Record does not exist |
314
+ | `500 Internal Server Error` | Database operation fails |
315
+
316
+ Error messages are returned as plain text in the response body.
317
+
318
+ ## Next Steps
319
+
320
+ See the [access rights documentation](./4.access-rights.md) to learn how to control field read/write permissions.
@@ -0,0 +1,148 @@
1
+ # Access Rights
2
+
3
+ ## Overview
4
+
5
+ The `@Access` decorator controls how fields are read and written through the API. Each field can be set to read-only, write-only, or read-write, providing fine-grained control over data exposure. Access modes can also be overridden on a per-action basis, allowing different permissions for `get`, `list`, `new`, and `edit` operations.
6
+
7
+ ## Access Modes
8
+
9
+ The `AccessMode` enum defines three permission levels:
10
+
11
+ ```typescript
12
+ import { Access, AccessMode } from "@antelopejs/interface-data-api/metadata";
13
+ ```
14
+
15
+ | Mode | Value | Description |
16
+ | ---- | ----- | ----------- |
17
+ | `AccessMode.ReadOnly` | `1` | Field appears in GET and LIST responses but is ignored in POST and PUT bodies |
18
+ | `AccessMode.WriteOnly` | `2` | Field is accepted in POST and PUT bodies but never appears in responses |
19
+ | `AccessMode.ReadWrite` | `3` | Field is both readable and writable |
20
+
21
+ ## Basic Usage
22
+
23
+ ### Read-Only Fields
24
+
25
+ Read-only fields appear in API responses but cannot be modified through create or update requests. Values sent for these fields in request bodies are silently ignored.
26
+
27
+ ```typescript
28
+ @RegisterDataController()
29
+ class UserAPI extends DataController(
30
+ User,
31
+ DefaultRoutes.All,
32
+ Controller("/users"),
33
+ ) {
34
+ @ModelReference()
35
+ @Model(UserModel, "my-database")
36
+ declare userModel: UserModel;
37
+
38
+ @Access(AccessMode.ReadOnly)
39
+ declare _id: string;
40
+
41
+ @Access(AccessMode.ReadOnly)
42
+ declare createdAt: string;
43
+
44
+ @Access(AccessMode.ReadWrite)
45
+ declare name: string;
46
+ }
47
+ ```
48
+
49
+ A PUT request to `/users/edit?id=user-123` with `{ "createdAt": "2025-01-01", "name": "Alice" }` updates only `name`. The `createdAt` field is ignored.
50
+
51
+ ### Write-Only Fields
52
+
53
+ Write-only fields accept input during create and update operations but are never included in API responses. This is useful for sensitive data like passwords.
54
+
55
+ ```typescript
56
+ @RegisterDataController()
57
+ class UserAPI extends DataController(
58
+ User,
59
+ DefaultRoutes.All,
60
+ Controller("/users"),
61
+ ) {
62
+ @ModelReference()
63
+ @Model(UserModel, "my-database")
64
+ declare userModel: UserModel;
65
+
66
+ @Access(AccessMode.ReadWrite)
67
+ declare name: string;
68
+
69
+ @Access(AccessMode.ReadWrite)
70
+ declare email: string;
71
+
72
+ @Access(AccessMode.WriteOnly)
73
+ declare password: string;
74
+ }
75
+ ```
76
+
77
+ A GET request to `/users/get?id=user-123` returns `name` and `email` but never includes `password`:
78
+
79
+ ```json
80
+ {
81
+ "name": "Bob",
82
+ "email": "bob@example.com"
83
+ }
84
+ ```
85
+
86
+ ### Read-Write Fields
87
+
88
+ Read-write fields are fully accessible: they appear in responses and accept input in request bodies.
89
+
90
+ ```typescript
91
+ @Access(AccessMode.ReadWrite)
92
+ declare email: string;
93
+
94
+ @Access(AccessMode.ReadWrite)
95
+ declare name: string;
96
+ ```
97
+
98
+ ## Per-Action Overrides
99
+
100
+ The `@Access` decorator accepts an optional second argument to override the access mode for specific actions. This allows a field to behave differently depending on the operation being performed.
101
+
102
+ ```typescript
103
+ @Access(defaultMode: AccessMode, overrides?: Record<string, AccessMode>)
104
+ ```
105
+
106
+ The `overrides` object maps action names (`"get"`, `"list"`, `"new"`, `"edit"`) to an `AccessMode` value. Actions not listed in the overrides use the default mode.
107
+
108
+ ### Example: Writable on Create, Read-Only on Edit
109
+
110
+ ```typescript
111
+ @RegisterDataController()
112
+ class UserAPI extends DataController(
113
+ User,
114
+ DefaultRoutes.All,
115
+ Controller("/users"),
116
+ ) {
117
+ @ModelReference()
118
+ @Model(UserModel, "my-database")
119
+ declare userModel: UserModel;
120
+
121
+ @Access(AccessMode.ReadOnly)
122
+ declare _id: string;
123
+
124
+ @Access(AccessMode.ReadWrite)
125
+ declare name: string;
126
+
127
+ // role can be set when creating, but cannot be changed during edit
128
+ @Access(AccessMode.ReadWrite, { edit: AccessMode.ReadOnly })
129
+ declare role: string;
130
+
131
+ // age is read-only by default, but writable during creation
132
+ @Access(AccessMode.ReadOnly, { new: AccessMode.ReadWrite })
133
+ declare age: number;
134
+ }
135
+ ```
136
+
137
+ In this configuration:
138
+
139
+ - `role` is readable and writable in all operations except `edit`, where it becomes read-only.
140
+ - `age` is read-only by default but can be set during `new` (create) operations.
141
+
142
+ ## Fields Without `@Access`
143
+
144
+ Fields declared without the `@Access` decorator are not exposed through the API at all. They do not appear in responses and are not accepted in request bodies. To include a field in the API, you must explicitly set its access mode.
145
+
146
+ ## Next Steps
147
+
148
+ See the [validators documentation](./5.validators.md) to learn how to enforce validation rules on field values.
@@ -0,0 +1,204 @@
1
+ # Validators
2
+
3
+ ## Overview
4
+
5
+ Validators provide field-level data integrity checks that run automatically during create and update operations. The `@Validator` decorator attaches a custom validation function to a field, while `@Mandatory` ensures that specific fields are present in request bodies for given operations. The `@Optional` decorator explicitly marks a field as not mandatory.
6
+
7
+ ## The `@Validator` Decorator
8
+
9
+ ### Basic Usage
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.
12
+
13
+ ```typescript
14
+ import { Validator } from "@antelopejs/interface-data-api/metadata";
15
+
16
+ @RegisterDataController()
17
+ class UserAPI extends DataController(
18
+ User,
19
+ DefaultRoutes.All,
20
+ Controller("/users"),
21
+ ) {
22
+ @ModelReference()
23
+ @Model(UserModel, "my-database")
24
+ declare userModel: UserModel;
25
+
26
+ @Access(AccessMode.ReadWrite)
27
+ @Validator((value) => typeof value === "string" && value.trim().length > 0)
28
+ declare name: string;
29
+
30
+ @Access(AccessMode.ReadWrite)
31
+ @Validator((value) => typeof value === "number" && value >= 0 && value <= 120)
32
+ declare age: number;
33
+ }
34
+ ```
35
+
36
+ ### Validation Function Signature
37
+
38
+ ```typescript
39
+ type ValidatorFunction = (value: unknown) => boolean | Promise<boolean>;
40
+ ```
41
+
42
+ The function:
43
+
44
+ - 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.
47
+ - Should handle unexpected types gracefully (e.g., check `typeof` before accessing properties).
48
+
49
+ ## Common Validation Patterns
50
+
51
+ ### String Validation
52
+
53
+ ```typescript
54
+ // Minimum length
55
+ @Validator((value) => typeof value === "string" && value.length >= 2)
56
+ declare firstName: string;
57
+
58
+ // Email format
59
+ const emailRegex = /^[\w](\.?[\w-]+)*@(?:[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,}$/;
60
+ @Validator((value) => typeof value === "string" && emailRegex.test(value))
61
+ declare email: string;
62
+
63
+ // Enum-like constraint
64
+ const VALID_ROLES = ["admin", "editor", "viewer"];
65
+ @Validator((value) => typeof value === "string" && VALID_ROLES.includes(value))
66
+ declare role: string;
67
+ ```
68
+
69
+ ### Number Validation
70
+
71
+ ```typescript
72
+ // Range
73
+ @Validator((value) => typeof value === "number" && value >= 0 && value <= 120)
74
+ declare age: number;
75
+
76
+ // Integer only
77
+ @Validator((value) => typeof value === "number" && Number.isInteger(value))
78
+ declare quantity: number;
79
+
80
+ // Positive
81
+ @Validator((value) => typeof value === "number" && value > 0)
82
+ declare price: number;
83
+ ```
84
+
85
+ ### Date Validation
86
+
87
+ ```typescript
88
+ // Valid date string
89
+ @Validator((value) => typeof value === "string" && !isNaN(Date.parse(value)))
90
+ declare birthDate: string;
91
+
92
+ // Future date only
93
+ @Validator((value) =>
94
+ typeof value === "string" &&
95
+ !isNaN(Date.parse(value)) &&
96
+ new Date(value) > new Date()
97
+ )
98
+ declare appointmentDate: string;
99
+ ```
100
+
101
+ ### Async Validation
102
+
103
+ ```typescript
104
+ // Check uniqueness against the database
105
+ @Validator(async (value) => {
106
+ if (typeof value !== "string") return false;
107
+ const existing = await lookupByEmail(value);
108
+ return !existing;
109
+ })
110
+ declare email: string;
111
+ ```
112
+
113
+ ## The `@Mandatory` Decorator
114
+
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.
116
+
117
+ ```typescript
118
+ import { Mandatory } from "@antelopejs/interface-data-api/metadata";
119
+ ```
120
+
121
+ ### Usage
122
+
123
+ Pass one or more operation names as arguments. Common operation names are `"new"` (create) and `"edit"` (update).
124
+
125
+ ```typescript
126
+ @RegisterDataController()
127
+ class UserAPI extends DataController(
128
+ User,
129
+ DefaultRoutes.All,
130
+ Controller("/users"),
131
+ ) {
132
+ @ModelReference()
133
+ @Model(UserModel, "my-database")
134
+ declare userModel: UserModel;
135
+
136
+ @Access(AccessMode.ReadWrite)
137
+ @Mandatory("new", "edit")
138
+ declare email: string;
139
+
140
+ @Access(AccessMode.WriteOnly)
141
+ @Mandatory("new")
142
+ declare password: string;
143
+
144
+ @Access(AccessMode.ReadWrite)
145
+ @Mandatory("edit")
146
+ declare status: string;
147
+
148
+ @Access(AccessMode.ReadWrite)
149
+ declare notes: string; // Not mandatory for any operation
150
+ }
151
+ ```
152
+
153
+ In this example:
154
+
155
+ - `email` must be provided in both create and update requests.
156
+ - `password` is required only when creating a new record.
157
+ - `status` is required only when editing an existing record.
158
+ - `notes` is always optional.
159
+
160
+ ### Skip Mandatory Validation
161
+
162
+ Use `DefaultRoutes.WithOptions` with the `noMandatory` option to create an endpoint that skips mandatory field checks.
163
+
164
+ ```typescript
165
+ const routes = {
166
+ edit: DefaultRoutes.Edit,
167
+ patchEdit: DefaultRoutes.WithOptions(DefaultRoutes.Edit, {
168
+ noMandatory: "true",
169
+ }),
170
+ };
171
+
172
+ @RegisterDataController()
173
+ class UserAPI extends DataController(User, routes, Controller("/users")) {
174
+ @Access(AccessMode.ReadWrite)
175
+ @Mandatory("edit")
176
+ declare status: string;
177
+ }
178
+ ```
179
+
180
+ Requests to `/users/edit` require `status`, while requests to `/users/patchEdit` do not.
181
+
182
+ ## The `@Optional` Decorator
183
+
184
+ The `@Optional` decorator explicitly marks a field as not mandatory for any operation. Use it on fields that have no other decorators but still need to be recognized by the Data API metadata system.
185
+
186
+ ```typescript
187
+ import { Optional } from "@antelopejs/interface-data-api/metadata";
188
+
189
+ @Optional()
190
+ declare notes: string;
191
+ ```
192
+
193
+ ## Error Handling
194
+
195
+ When validation or mandatory checks fail, the API returns a `400 Bad Request` response with a plain text message describing the issue.
196
+
197
+ - **Mandatory failure:** `"Missing mandatory fields: email, password"`
198
+ - **Validator failure:** `"Invalid field type(s): age, email"`
199
+
200
+ All fields are checked, and the error message lists every field that failed validation.
201
+
202
+ ## Next Steps
203
+
204
+ See the [listable fields documentation](./6.listable.md) to learn how to control field visibility in list operations and configure pagination.