@antelopejs/interface-data-api 0.2.1 → 0.3.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.
Files changed (48) hide show
  1. package/README.md +131 -21
  2. package/dist/antelope.test.js +1 -1
  3. package/dist/components.d.ts +64 -15
  4. package/dist/components.js +91 -23
  5. package/dist/components.js.map +1 -1
  6. package/dist/index.d.ts +9 -5
  7. package/dist/index.js +42 -56
  8. package/dist/index.js.map +1 -1
  9. package/dist/metadata.d.ts +23 -3
  10. package/dist/metadata.js +32 -7
  11. package/dist/metadata.js.map +1 -1
  12. package/dist/tests/components/foreign_computed.test.d.ts +1 -0
  13. package/dist/tests/components/foreign_computed.test.js +172 -0
  14. package/dist/tests/components/foreign_computed.test.js.map +1 -0
  15. package/dist/tests/components/mandatory.test.js +3 -5
  16. package/dist/tests/components/mandatory.test.js.map +1 -1
  17. package/dist/tests/components/parsed_value.test.js +2 -2
  18. package/dist/tests/components/parsed_value.test.js.map +1 -1
  19. package/dist/tests/components/partial_edit.test.js +2 -2
  20. package/dist/tests/components/partial_edit.test.js.map +1 -1
  21. package/dist/tests/components/per_action_access.test.js +12 -2
  22. package/dist/tests/components/per_action_access.test.js.map +1 -1
  23. package/dist/tests/components/validator.test.js +3 -5
  24. package/dist/tests/components/validator.test.js.map +1 -1
  25. package/dist/tests/index/inheritance.test.js +1 -1
  26. package/dist/tests/index/inheritance.test.js.map +1 -1
  27. package/dist/tests/index/routes.test.js +45 -3
  28. package/dist/tests/index/routes.test.js.map +1 -1
  29. package/dist/tests/integration.test.js +5 -7
  30. package/dist/tests/integration.test.js.map +1 -1
  31. package/dist/tests/utils.d.ts +6 -1
  32. package/dist/tests/utils.js +3 -1
  33. package/dist/tests/utils.js.map +1 -1
  34. package/docs/1.introduction.md +26 -26
  35. package/docs/10.joined.md +36 -5
  36. package/docs/11.computed.md +6 -6
  37. package/docs/2.data-controllers.md +4 -4
  38. package/docs/3.routes.md +85 -11
  39. package/docs/4.access-rights.md +21 -6
  40. package/docs/5.validators.md +2 -0
  41. package/docs/6.listable.md +5 -2
  42. package/docs/7.foreign-keys.md +9 -10
  43. package/docs/8.filters.md +96 -20
  44. package/docs/9.modifiers.md +2 -4
  45. package/docs/assets/joined-list.svg +23 -0
  46. package/docs/tsconfig.json +12 -0
  47. package/package.json +21 -15
  48. package/skills/data-api-interface/SKILL.md +9 -0
@@ -6,7 +6,7 @@
6
6
  * context wrappers) do not change the result.
7
7
  */
8
8
  export declare function getFunctionName(): string;
9
- export declare function request(functionName: string, uri: string, method: string, payload?: unknown, queryParams?: Record<string, string>): Promise<Response>;
9
+ export declare function request(functionName: string, uri: string, method: string, payload?: unknown, queryParams?: Record<string, string> | [string, string][]): Promise<Response>;
10
10
  export declare function newRequest(functionName: string, payload: unknown, queryParams?: Record<string, string>): Promise<Response>;
11
11
  export declare function getRequest(functionName: string, queryParams?: Record<string, string>): Promise<Response>;
12
12
  export declare function listRequest(functionName: string, queryParams?: Record<string, string>): Promise<Response>;
@@ -17,3 +17,8 @@ export declare function getSchemaInstance(schemaName: string): import("@antelope
17
17
  export declare function validateObjectList<T extends {
18
18
  _id: string;
19
19
  }>(objectList: T[], expectedObjectList: Partial<T>[], fieldsToCheck: (keyof T)[]): Promise<void>;
20
+ /** What the `new` route answers: the created row, with its key. */
21
+ export interface CreatedRow {
22
+ _id: string;
23
+ [field: string]: unknown;
24
+ }
@@ -62,7 +62,9 @@ function getFunctionName() {
62
62
  const caller = captureCallSites().find(isTestCallSite);
63
63
  return caller?.getFunctionName() ?? UNKNOWN_FUNCTION_NAME;
64
64
  }
65
- async function request(functionName, uri, method, payload, queryParams) {
65
+ async function request(functionName, uri, method, payload,
66
+ // Pairs repeat a key (`filter_x=a&filter_x=b`).
67
+ queryParams) {
66
68
  return await fetch(`${constants_1.URL_BASE}/${functionName}/${uri}?${new URLSearchParams(queryParams).toString()}`, {
67
69
  method,
68
70
  headers: {
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/tests/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/tests/utils.ts"],"names":[],"mappings":"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"}
@@ -1,35 +1,23 @@
1
- # Data API Documentation
1
+ # Expose database records through an API
2
2
 
3
3
  ## Overview
4
4
 
5
- The Data API interface provides a declarative approach to building RESTful APIs with automatic CRUD operations. It connects your database models directly to API endpoints, handling validation, access control, foreign key resolution, and pagination with minimal boilerplate.
5
+ Use `@antelopejs/interface-data-api` to expose a table through named CRUD routes and an explicit field policy. A data controller connects the table model to HTTP handlers; its metadata decides which fields clients may read, write, list, and filter.
6
6
 
7
- ## Key Features
7
+ ## Build one resource first
8
8
 
9
- - **Database-to-API Mapping** - Expose database tables as REST endpoints through data controllers.
10
- - **Default CRUD Routes** - Built-in `get`, `list`, `new`, `edit`, and `delete` operations that work out of the box.
11
- - **Access Control** - Define read-only, write-only, or read-write permissions per field, with per-action overrides.
12
- - **Field Validation** - Attach custom validation functions to individual fields and enforce mandatory fields per operation.
13
- - **Listable Fields and Pagination** - Control which fields appear in list responses, with support for multiple pluck modes and paginated results.
14
- - **Sorting** - Mark fields as sortable to enable ordering in list queries.
15
- - **Foreign Key Resolution** - Automatically resolve relationships between tables with optional field plucking.
16
- - **Joined Fields** - Flatten a scalar field from another table onto each row so it can be sorted, filtered, and searched natively in the database.
17
- - **Computed Fields** - Merge in-database computed expressions (row-local values or aggregates over other tables) onto each row, natively sortable, filterable, and pageable.
18
- - **Filters** - Add default or custom filter logic to list endpoints using query parameters.
19
- - **Modifier Integration** - Automatic data transformation (encryption, hashing, localization) through database-decorators.
20
-
21
- ## Dependencies
22
-
23
- This interface relies on the following AntelopeJS packages:
24
-
25
- - [@antelopejs/interface-api](https://github.com/AntelopeJS/interface-api) - API controllers and route registration.
26
- - [@antelopejs/interface-database-decorators](https://github.com/AntelopeJS/interface-database-decorators) - Database table definitions and models.
9
+ 1. **Prepare the database.** Define a table and register its schema with database-decorators. Your AntelopeJS module needs a database provider and an API provider.
10
+ 2. **Choose the routes.** Start with the [route set](./3.routes.md) your clients need, rather than exposing every write operation by default.
11
+ 3. **Set the field policy.** Add [access rights](./4.access-rights.md), [validators](./5.validators.md), and [list fields](./6.listable.md). Field permissions are not user authentication or row-level authorization.
12
+ 4. **Check one read and one rejected write.** Confirm the response excludes private fields and that invalid input cannot reach storage. Then add [relationships](./7.foreign-keys.md) and [filters](./8.filters.md).
27
13
 
28
14
  ## Quick Start
29
15
 
30
- The following example creates a full CRUD API for a `users` table.
16
+ The following example declares all five CRUD routes for a `users` table. It assumes a configured provider and an existing `my-database` instance. Import the table declaration before calling `await RegisterSchema("myapp")` in the declaring module's initialization; await that setup before serving requests.
31
17
 
32
- ```typescript
18
+ This is a field-policy example, not a production account system: write-only prevents a password from appearing in responses, but does not hash it or authenticate requests. Add an appropriate [database modifier](./9.modifiers.md) and your authentication policy before accepting real credentials.
19
+
20
+ ```ts docs-check
33
21
  import { Controller } from "@antelopejs/interface-api";
34
22
  import {
35
23
  DataController,
@@ -53,9 +41,8 @@ import {
53
41
  } from "@antelopejs/interface-database-decorators";
54
42
 
55
43
  // Define the database table
56
- @RegisterTable("users")
44
+ @RegisterTable("users", "myapp")
57
45
  class User extends Table {
58
- @Index({ primary: true })
59
46
  @Field("string")
60
47
  declare _id: string;
61
48
 
@@ -114,13 +101,26 @@ This generates the following REST endpoints:
114
101
  | PUT | `/users/edit?id=<id>` | Update an existing user |
115
102
  | DELETE | `/users/delete?id=<id>` | Delete a user |
116
103
 
117
- ## Documentation Sections
104
+ ### Check the field policy
105
+
106
+ Use disposable data and your configured API base URL. Create a user with `name`, `email`, and a dummy `password`; the response is an array of inserted IDs. Use the first ID for the following checks:
107
+
108
+ 1. `GET /users/get?id=<id>` returns the readable fields, never `password`.
109
+ 2. `GET /users/list?limit=10` includes `_id`, `name`, and `email` in `results`.
110
+ 3. `PUT /users/edit?id=<id>` with `{ "name": "Updated" }` preserves the omitted email. With `{ "email": "" }`, it returns `400` because email is mandatory on edit.
111
+
112
+ `@Mandatory` does not check email syntax or password strength. Add [validators](./5.validators.md) before accepting untrusted input.
113
+
114
+ ## Core guides
118
115
 
119
116
  - [Data Controllers](./2.data-controllers.md) - Create and configure data controllers
120
117
  - [Routes](./3.routes.md) - Default routes, custom routes, and route options
121
118
  - [Access Rights](./4.access-rights.md) - Control field read/write permissions
122
119
  - [Validators](./5.validators.md) - Field-level validation and mandatory fields
123
120
  - [Listable Fields](./6.listable.md) - Pagination, pluck modes, and sorting
121
+
122
+ ## Relationships and advanced queries
123
+
124
124
  - [Foreign Keys](./7.foreign-keys.md) - Establish relationships between tables
125
125
  - [Filters](./8.filters.md) - Add filtering capabilities to list endpoints
126
126
  - [Modifiers](./9.modifiers.md) - Automatic data transformation with database-decorators
package/docs/10.joined.md CHANGED
@@ -10,19 +10,19 @@ Joined fields are read-only: the value is set by a lookup at query time and is n
10
10
 
11
11
  Apply `@Joined` to a field that should be populated from a remote table. The local row must carry a foreign key (`localKey`) that matches the remote table's `remoteIndex`.
12
12
 
13
+ Use [foreign keys](./7.foreign-keys.md) when the response needs a nested record instead. The snippets below assume the controller imports and model from the [quick start](./1.introduction.md#quick-start), with `myapp` registered before requests are served.
14
+
13
15
  ```typescript
14
16
  import { Joined } from "@antelopejs/interface-data-api/metadata";
15
17
  import {
16
18
  Field,
17
- Index,
18
19
  RegisterTable,
19
20
  Relation,
20
21
  Table,
21
22
  } from "@antelopejs/interface-database-decorators";
22
23
 
23
- @RegisterTable("users")
24
+ @RegisterTable("users", "myapp")
24
25
  class User extends Table {
25
- @Index({ primary: true })
26
26
  @Field("string")
27
27
  declare _id: string;
28
28
 
@@ -30,9 +30,8 @@ class User extends Table {
30
30
  declare name: string;
31
31
  }
32
32
 
33
- @RegisterTable("orders")
33
+ @RegisterTable("orders", "myapp")
34
34
  class Order extends Table {
35
- @Index({ primary: true })
36
35
  @Field("string")
37
36
  declare _id: string;
38
37
 
@@ -118,6 +117,12 @@ Joined fields are always sorted without a database index (`@Sortable` is forced
118
117
 
119
118
  ## Deferred Resolution on Lists
120
119
 
120
+ The default list route handles this scheduling for you. Continue with [cross-schema joins](#cross-schema-joins) unless you are building a custom route with `Query.List`.
121
+
122
+ ![Joined fields used by filters are resolved before counting; sort fields before paging; display-only fields after paging in the default list route.](./assets/joined-list.svg){width="480"}
123
+
124
+ ### Advanced: composing `Query.List`
125
+
121
126
  `Query.List` only materializes the joined groups that the request's sort or filters reference before the count. The remaining display-only groups are deferred: the returned stream resolves them itself as soon as an operation can observe them, so destructuring `[query, total]` alone always yields rows with every `@Joined` field resolved.
122
127
 
123
128
  - Paging the stream (`slice`, `nth`) appends the lookups after the page boundary, so they only run on the returned rows.
@@ -127,6 +132,32 @@ Joined fields are always sorted without a database index (`@Sortable` is forced
127
132
 
128
133
  The page-only benefit therefore holds as long as paging is the first thing the caller does. An operation applied to the whole stream before paging — a filter, a sort, or the `lookup` that `Query.Foreign` stages for a `@Foreign` field — materializes the joined groups over the whole matched set instead. The lookups still stay out of the count pipeline, but a caller that wants them strictly on the page must pass `{ exposeDeferredJoined: true }` to `Query.List` and resolve them itself after the slice: the raw stream is then returned unchanged and the third tuple element names the joined fields left to resolve with `Query.Joined`. This is what the default list route does, which also lets it limit the page lookups to the plucked fields.
129
134
 
135
+ ### Reading a page: `Query.ListPage`
136
+
137
+ A custom list route over `Query.List` reads its page with `Query.ListPage` rather than chaining the steps itself. It slices the stream, then resolves on the page only, in this order: the deferred joined groups, the `@Computed` fields the filters and the sort did not already merge, the `@Foreign` relations (last, since a computed expression may read a relation's key), and the pluck.
138
+
139
+ ```typescript
140
+ const [query, total, deferredJoined] = Query.List(
141
+ this,
142
+ meta,
143
+ model.table,
144
+ ctx,
145
+ [params.sortKey, params.sortDirection],
146
+ params.filters,
147
+ model.database,
148
+ { exposeDeferredJoined: true },
149
+ );
150
+ const rows = await Query.ListPage(model.database, meta, query, {
151
+ offset: params.offset ?? 0,
152
+ limit: params.limit ?? 10,
153
+ filters: params.filters,
154
+ sortKey: params.sortKey,
155
+ pluck, // Set of field names; every field when unset
156
+ noForeign: params.noForeign,
157
+ deferredJoined,
158
+ });
159
+ ```
160
+
130
161
  ## Cross-Schema Joins
131
162
 
132
163
  By default, the remote table is resolved in the same schema as the controller's table. Use the `schema` option to join against a table registered in a different schema; the join is resolved through the remote table's class, against the default instance of the schema `RegisterSchema` bound it to. The remote class must be registered with `@RegisterTable` before the field is declared; a string table name with no registered class in that schema throws at declaration.
@@ -10,10 +10,12 @@ Computed fields are read-only: the value is produced by the query and is never p
10
10
 
11
11
  The expression receives the row proxy and the schema instance of the controller's table. It must return a value built from the query builder (`ValueProxy` operations or a subquery), not plain JavaScript computed at request time.
12
12
 
13
+ The snippets extend the [quick-start controller pattern](./1.introduction.md#quick-start); define `GroupModel` with `BasicDataModel(Group)` and register the `myapp` schema before serving requests. Check [driver support](#driver-support) before using aggregate subqueries.
14
+
13
15
  ```typescript
14
16
  import { Computed } from "@antelopejs/interface-data-api/metadata";
15
17
 
16
- @RegisterTable("groups")
18
+ @RegisterTable("groups", "myapp")
17
19
  class Group extends Table {
18
20
  @Field("string")
19
21
  declare _id: string;
@@ -22,7 +24,7 @@ class Group extends Table {
22
24
  declare name: string;
23
25
  }
24
26
 
25
- @RegisterTable("devices")
27
+ @RegisterTable("devices", "myapp")
26
28
  class Device extends Table {
27
29
  @Field("string")
28
30
  declare _id: string;
@@ -116,15 +118,13 @@ Computed fields are always sorted without a database index (`@Sortable` is force
116
118
 
117
119
  ### Filtering on numeric computed fields
118
120
 
119
- Filter values arrive as strings from the query string. The default filter compares them as-is, which never matches a numeric computed value (`"0"` is not `0` in BSON). Provide a custom filter function that coerces the value:
121
+ Filter values arrive as strings from the query string. Use the validated `numberFilter` from [Numeric and Boolean Filters](./8.filters.md#numeric-and-boolean-filters), imported into the controller file. It rejects invalid numbers and honors the requested comparison operator; `parseFloat` alone accepts partial input such as `"3devices"`.
120
122
 
121
123
  ```typescript
122
124
  @Computed((row, db) => db.table("devices").getAll(row.key("_id"), "group_id").count(), {
123
125
  default: 0,
124
126
  })
125
- @Filter((_context, proxy, _key, value) =>
126
- (proxy as ValueProxy<number>).eq(parseFloat(value)),
127
- )
127
+ @Filter(numberFilter, false)
128
128
  declare devices_count: number;
129
129
  ```
130
130
 
@@ -8,7 +8,9 @@ A data controller is the central building block of the Data API. It connects a d
8
8
 
9
9
  Use the `DataController` function to define a new controller. It accepts three arguments: the table class, a route definition object, and a controller base with the endpoint path.
10
10
 
11
- ```typescript
11
+ This example uses the `myapp` schema and the `my-database` instance. Import the table declaration and await `RegisterSchema("myapp")` in the declaring module before serving requests; provision the named instance through your database setup.
12
+
13
+ ```ts docs-check
12
14
  import { Controller } from "@antelopejs/interface-api";
13
15
  import {
14
16
  DataController,
@@ -24,16 +26,14 @@ import {
24
26
  import {
25
27
  BasicDataModel,
26
28
  Field,
27
- Index,
28
29
  Model,
29
30
  RegisterTable,
30
31
  Table,
31
32
  } from "@antelopejs/interface-database-decorators";
32
33
 
33
34
  // Define the database table
34
- @RegisterTable("users")
35
+ @RegisterTable("users", "myapp")
35
36
  class User extends Table {
36
- @Index({ primary: true })
37
37
  @Field("string")
38
38
  declare _id: string;
39
39
 
package/docs/3.routes.md CHANGED
@@ -138,10 +138,14 @@ Content-Type: application/json
138
138
  }
139
139
  ```
140
140
 
141
- **Response:**
141
+ **Response:** the created record, as [Get](#get) answers it, with its `_id` even when `get` does not read it.
142
142
 
143
143
  ```json
144
- ["user-789"]
144
+ {
145
+ "_id": "user-789",
146
+ "name": "Bob",
147
+ "email": "bob@example.com"
148
+ }
145
149
  ```
146
150
 
147
151
  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`.
@@ -184,10 +188,14 @@ Content-Type: application/json
184
188
  }
185
189
  ```
186
190
 
187
- **Response:**
191
+ **Response:** the stored record after the update, as [Get](#get) answers it (joined, computed and foreign fields included), so the caller needs no second read.
188
192
 
189
- ```http
190
- 200 OK
193
+ ```json
194
+ {
195
+ "_id": "user-123",
196
+ "name": "Robert",
197
+ "email": "robert@example.com"
198
+ }
191
199
  ```
192
200
 
193
201
  Before updating, the `update` event is triggered on any table modifiers attached to the database table.
@@ -258,12 +266,13 @@ class UserAPI extends DataController(User, routes, Controller("/users")) {
258
266
 
259
267
  **List:**
260
268
 
261
- | Option | Type | Description |
262
- | ----------- | --------- | ------------------------------------------------------ |
263
- | `maxPage` | `number` | Maximum allowed value for `limit` (caps the page size) |
264
- | `noForeign` | `boolean` | Skip foreign key resolution |
265
- | `noPluck` | `boolean` | Return all database fields instead of plucked fields |
266
- | `pluckMode` | `string` | Select a named pluck mode (see `@Listable`) |
269
+ | Option | Type | Description |
270
+ | ----------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
271
+ | `maxPage` | `number` | Maximum allowed value for `limit` (caps the page size) |
272
+ | `noForeign` | `boolean` | Skip foreign key resolution |
273
+ | `noPluck` | `boolean` | Return all database fields instead of plucked fields |
274
+ | `pluckMode` | `string` | Select a named pluck mode (see `@Listable`) |
275
+ | `filters` | `Record<string, FieldFilter>` | Mandatory filters merged with request filters; route field keys win (see [filters](./8.filters.md#mandatory-route-filters)). |
267
276
 
268
277
  **New:**
269
278
 
@@ -278,6 +287,71 @@ class UserAPI extends DataController(User, routes, Controller("/users")) {
278
287
  | `index` | `string` | Use a secondary index instead of the primary key |
279
288
  | `noMandatory` | `string` | Skip mandatory field validation |
280
289
 
290
+ ## Amending the Body of New and Edit
291
+
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
+
294
+ ```ts docs-check
295
+ import { DefaultRoutes } from "@antelopejs/interface-data-api";
296
+ import {
297
+ type Parameters,
298
+ Validation,
299
+ } from "@antelopejs/interface-data-api/components";
300
+ import type { RequestContext } from "@antelopejs/interface-api";
301
+
302
+ const stampedNew = {
303
+ ...DefaultRoutes.New,
304
+ func: function (
305
+ this: unknown,
306
+ ctx: RequestContext,
307
+ params: Parameters.NewParameters,
308
+ body: Buffer,
309
+ ) {
310
+ const parsed = Validation.ParseBody(body);
311
+ parsed.source = "import";
312
+ return DefaultRoutes.New.func.call(this, ctx, params, parsed);
313
+ },
314
+ };
315
+ ```
316
+
317
+ ## Loaded-Model Guards with `WithGetGuard`
318
+
319
+ `DefaultRoutes.WithGetGuard(guard)` returns a Get route that awaits a guard after loading and unlocking the model, but before selecting readable fields and applying response getters. Use it to check a stored discriminator or ownership field that must not appear in the response.
320
+
321
+ ```ts docs-check
322
+ import { DefaultRoutes } from "@antelopejs/interface-data-api";
323
+ import { assert } from "@antelopejs/interface-api-util";
324
+
325
+ const invoiceGet = DefaultRoutes.WithGetGuard(
326
+ async function (ctx, current, params) {
327
+ // documentType can be stored without being exposed through @Access.
328
+ assert(current.documentType === "invoice", 404, "Not Found");
329
+ },
330
+ );
331
+
332
+ // Pass this route map to DataController(Document, routes, Controller("/invoices")).
333
+ const routes = {
334
+ get: DefaultRoutes.WithOptions(invoiceGet, { noForeign: "true" }),
335
+ };
336
+ ```
337
+
338
+ The `GetGuard` callback receives `(ctx, current, params)`. Declare it as a regular function to access the controller through `this`.
339
+
340
+ | Argument | Contents | Use it for |
341
+ | --------- | --------------------------------------------------------------------------------------- | ---------------------------------------------- |
342
+ | `ctx` | Request context | Request-specific policy. |
343
+ | `current` | Loaded model, including non-readable fields and normal Get joined/computed/foreign data | Inspecting stored ownership or discriminators. |
344
+ | `params` | `Parameters.GetParameters` | The parsed Get parameters. |
345
+
346
+ Treat `current` as read-only: it is not the response DTO and the API does not freeze it.
347
+
348
+ Keep these boundaries when writing a guard:
349
+
350
+ - **Deny by throwing.** Throw an `HTTPResult` or use `assert`. A normal return permits the read, including a return value of `false`.
351
+ - **Missing records bypass the guard.** The route returns `404` without invoking it.
352
+ - **Use the already-loaded model.** The guard does not issue a second read or change field-access rules.
353
+ - **Protect each route deliberately.** The guard applies only to the route using it, not list, edit, or delete routes.
354
+
281
355
  ## Custom Routes
282
356
 
283
357
  For operations beyond CRUD, define a custom route callback with `func`, `args`, and `method` properties.
@@ -4,6 +4,19 @@
4
4
 
5
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
6
 
7
+ ## Separate field policy from caller permissions
8
+
9
+ `@Access` answers “may this action read or write this field?”, not “may this user access this record?”. Authenticate the caller and apply record-level policy separately. A write-only password is hidden from responses, but this decorator does not hash it.
10
+
11
+ Build the field policy in this order:
12
+
13
+ 1. Set `@Access` for each public field. Undecorated fields remain private.
14
+ 2. Add action overrides for differences between creation and editing.
15
+ 3. Add `@Listable` for the fields selected by list responses; readable alone does not define a list projection.
16
+ 4. Add validators and mandatory-field rules for writable inputs.
17
+
18
+ For example, a display name can be readable and writable, an ID readable only, and a password writable only. Test each direction: attempting to write a read-only field is ignored, while omitting a mandatory writable field is a validation concern.
19
+
7
20
  ## Access Modes
8
21
 
9
22
  The `AccessMode` enum defines three permission levels:
@@ -12,11 +25,11 @@ The `AccessMode` enum defines three permission levels:
12
25
  import { Access, AccessMode } from "@antelopejs/interface-data-api/metadata";
13
26
  ```
14
27
 
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 |
28
+ | Mode | Value | Description |
29
+ | ---------------------- | ----- | --------------------------------------------------------------------------------------------------- |
30
+ | `AccessMode.ReadOnly` | `1` | Field is readable; list responses also require selection by the pluck mode. POST and PUT ignore it. |
31
+ | `AccessMode.WriteOnly` | `2` | Field is accepted in POST and PUT bodies but never appears in responses |
32
+ | `AccessMode.ReadWrite` | `3` | Field is both readable and writable |
20
33
 
21
34
  ## Basic Usage
22
35
 
@@ -105,6 +118,8 @@ The `@Access` decorator accepts an optional second argument to override the acce
105
118
 
106
119
  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
120
 
121
+ To read the access a field has in one action, outside the routes (to render a form read-only on edit, for example), call `GetEffectiveMode(field, action)` from `@antelopejs/interface-data-api/metadata` with the field's metadata (`meta.fields[name]`). It is the mode the routes apply.
122
+
108
123
  ### Example: Writable on Create, Read-Only on Edit
109
124
 
110
125
  ```typescript
@@ -141,7 +156,7 @@ In this configuration:
141
156
 
142
157
  ## Fields Without `@Access`
143
158
 
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.
159
+ Ordinary fields need `@Access` to appear in responses or accept input; `@Listable` alone does not grant access. The derived-field decorators [`@Joined`](./10.joined.md) and [`@Computed`](./11.computed.md) are exceptions: they set read-only access themselves.
145
160
 
146
161
  ## Next Steps
147
162
 
@@ -118,6 +118,8 @@ declare appointmentDate: string;
118
118
 
119
119
  ### Async Validation
120
120
 
121
+ An asynchronous lookup can give early feedback, but a check followed by an insert is not a uniqueness guarantee: concurrent requests can both pass. Enforce uniqueness in the database where supported, and handle write conflicts. `lookupByEmail` below is an application-supplied function.
122
+
121
123
  ```typescript
122
124
  // Check uniqueness against the database
123
125
  @Validator(async (value) => {
@@ -101,6 +101,7 @@ class UserAPI extends DataController(User, routes, Controller("/users")) {
101
101
  declare userModel: UserModel;
102
102
 
103
103
  @Listable()
104
+ @Listable(true, "detailed")
104
105
  @Access(AccessMode.ReadOnly)
105
106
  declare name: string;
106
107
 
@@ -113,6 +114,8 @@ class UserAPI extends DataController(User, routes, Controller("/users")) {
113
114
  - `GET /users/list` returns only `name`.
114
115
  - `GET /users/detailed` returns both `name` and `internalNotes`.
115
116
 
117
+ Modes are independent field sets: `"detailed"` does not inherit `"list"`. The example includes `name` in both modes explicitly. A registered list route whose `pluckMode` has never been declared returns `400 Bad Request` unless `noPluck` is enabled; an unregistered URL instead returns `404`.
118
+
116
119
  ### Listable with Getters
117
120
 
118
121
  When a field uses a getter (computed property), the `@Listable` decorator must specify which database fields the getter depends on. This ensures those fields are fetched from the database.
@@ -221,13 +224,13 @@ class UserAPI extends DataController(
221
224
 
222
225
  ### The `noIndex` Option
223
226
 
224
- By default, `@Sortable()` assumes the field has a database index and uses indexed ordering. Set `noIndex: true` when the field is not indexed, so the framework performs an in-memory sort instead.
227
+ By default, `@Sortable()` marks the field for indexed ordering. Set `noIndex: true` when it is not indexed. Both paths build an AQL `orderBy` operation; this option changes where sorting is staged, not whether rows are sorted in application memory. The provider determines how the database executes that ordering.
225
228
 
226
229
  ```typescript
227
230
  @Sortable() // Uses database index for sorting
228
231
  declare _id: string;
229
232
 
230
- @Sortable({ noIndex: true }) // In-memory sort, no index required
233
+ @Sortable({ noIndex: true }) // Non-indexed query ordering
231
234
  declare name: string;
232
235
  ```
233
236
 
@@ -8,19 +8,21 @@ The `@Foreign` decorator establishes relationships between database tables in yo
8
8
 
9
9
  Apply `@Foreign` to a field that stores a foreign key value. The decorator replaces the raw ID in the response with the full referenced record.
10
10
 
11
+ Choose the response fields explicitly with `pluck`. A foreign lookup reads the target table, not its API controller: do not rely on the target controller's `@Access` policy to hide passwords or internal fields. The example below selects only the user's ID, name, and email.
12
+
13
+ The snippets use the `myapp` schema. Register it in the declaring module before serving requests, as in the [quick start](./1.introduction.md#quick-start). Controller snippets assume the corresponding model and shared imports are already declared.
14
+
11
15
  ```typescript
12
16
  import { Foreign } from "@antelopejs/interface-data-api/metadata";
13
17
  import {
14
18
  Field,
15
- Index,
16
19
  RegisterTable,
17
20
  Relation,
18
21
  Table,
19
22
  } from "@antelopejs/interface-database-decorators";
20
23
 
21
- @RegisterTable("users")
24
+ @RegisterTable("users", "myapp")
22
25
  class User extends Table {
23
- @Index({ primary: true })
24
26
  @Field("string")
25
27
  declare _id: string;
26
28
 
@@ -31,9 +33,8 @@ class User extends Table {
31
33
  declare email: string;
32
34
  }
33
35
 
34
- @RegisterTable("orders")
36
+ @RegisterTable("orders", "myapp")
35
37
  class Order extends Table {
36
- @Index({ primary: true })
37
38
  @Field("string")
38
39
  declare _id: string;
39
40
 
@@ -61,7 +62,7 @@ class OrderAPI extends DataController(
61
62
 
62
63
  @Listable()
63
64
  @Access(AccessMode.ReadOnly)
64
- @Foreign(User)
65
+ @Foreign(User, undefined, false, ["_id", "name", "email"])
65
66
  declare userId: User;
66
67
 
67
68
  @Listable()
@@ -125,9 +126,8 @@ When a field stores an array of foreign key IDs, set the `multi` parameter to `t
125
126
  The table-level `@Relation({ many: true })` is declarative metadata recording that the field holds multiple target keys; API-side resolution is controlled solely by the `multi` parameter of `@Foreign`.
126
127
 
127
128
  ```typescript
128
- @RegisterTable("carts")
129
+ @RegisterTable("carts", "myapp")
129
130
  class Cart extends Table {
130
- @Index({ primary: true })
131
131
  @Field("string")
132
132
  declare _id: string;
133
133
 
@@ -136,9 +136,8 @@ class Cart extends Table {
136
136
  declare items: string[]; // Array of CartItem IDs
137
137
  }
138
138
 
139
- @RegisterTable("cart_items")
139
+ @RegisterTable("cart_items", "myapp")
140
140
  class CartItem extends Table {
141
- @Index({ primary: true })
142
141
  @Field("string")
143
142
  declare _id: string;
144
143