@antelopejs/interface-data-api 0.1.9 → 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.
Files changed (67) hide show
  1. package/dist/antelope.test.js +4 -3
  2. package/dist/antelope.test.js.map +1 -1
  3. package/dist/components.d.ts +39 -8
  4. package/dist/components.js +106 -28
  5. package/dist/components.js.map +1 -1
  6. package/dist/index.d.ts +13 -31
  7. package/dist/index.js +20 -22
  8. package/dist/index.js.map +1 -1
  9. package/dist/metadata.d.ts +43 -6
  10. package/dist/metadata.js +6 -4
  11. package/dist/metadata.js.map +1 -1
  12. package/dist/tests/components/access_control.test.js +2 -2
  13. package/dist/tests/components/access_control.test.js.map +1 -1
  14. package/dist/tests/components/computed.test.js +2 -2
  15. package/dist/tests/components/computed.test.js.map +1 -1
  16. package/dist/tests/components/deferred_joined.test.js +3 -3
  17. package/dist/tests/components/deferred_joined.test.js.map +1 -1
  18. package/dist/tests/components/filter_options.test.js +1 -1
  19. package/dist/tests/components/filter_options.test.js.map +1 -1
  20. package/dist/tests/components/foreign_joined.test.js +2 -2
  21. package/dist/tests/components/foreign_joined.test.js.map +1 -1
  22. package/dist/tests/components/joined.test.js +3 -3
  23. package/dist/tests/components/joined.test.js.map +1 -1
  24. package/dist/tests/components/listable.test.js +2 -2
  25. package/dist/tests/components/listable.test.js.map +1 -1
  26. package/dist/tests/components/mandatory.test.js +29 -8
  27. package/dist/tests/components/mandatory.test.js.map +1 -1
  28. package/dist/tests/components/modifier_key.test.js +2 -2
  29. package/dist/tests/components/modifier_key.test.js.map +1 -1
  30. package/dist/tests/components/parsed_value.test.d.ts +1 -0
  31. package/dist/tests/components/parsed_value.test.js +152 -0
  32. package/dist/tests/components/parsed_value.test.js.map +1 -0
  33. package/dist/tests/components/partial_edit.test.d.ts +1 -0
  34. package/dist/tests/components/partial_edit.test.js +277 -0
  35. package/dist/tests/components/partial_edit.test.js.map +1 -0
  36. package/dist/tests/components/per_action_access.test.js +1 -1
  37. package/dist/tests/components/per_action_access.test.js.map +1 -1
  38. package/dist/tests/components/validator.test.js +1 -1
  39. package/dist/tests/components/validator.test.js.map +1 -1
  40. package/dist/tests/index/get_guard.test.js +2 -2
  41. package/dist/tests/index/get_guard.test.js.map +1 -1
  42. package/dist/tests/index/inheritance.test.d.ts +1 -0
  43. package/dist/tests/index/inheritance.test.js +122 -0
  44. package/dist/tests/index/inheritance.test.js.map +1 -0
  45. package/dist/tests/index/routes.test.js +2 -2
  46. package/dist/tests/index/routes.test.js.map +1 -1
  47. package/dist/tests/integration.test.js +1 -1
  48. package/dist/tests/integration.test.js.map +1 -1
  49. package/dist/tests/utils.d.ts +7 -0
  50. package/dist/tests/utils.js +48 -6
  51. package/dist/tests/utils.js.map +1 -1
  52. package/dist/tests/utils.test.d.ts +1 -0
  53. package/dist/tests/utils.test.js +28 -0
  54. package/dist/tests/utils.test.js.map +1 -0
  55. package/docs/1.introduction.md +7 -7
  56. package/docs/10.joined.md +13 -13
  57. package/docs/11.computed.md +11 -15
  58. package/docs/2.data-controllers.md +5 -5
  59. package/docs/3.routes.md +61 -49
  60. package/docs/4.access-rights.md +5 -5
  61. package/docs/5.validators.md +29 -8
  62. package/docs/6.listable.md +7 -7
  63. package/docs/7.foreign-keys.md +7 -7
  64. package/docs/8.filters.md +16 -16
  65. package/docs/9.modifiers.md +2 -2
  66. package/package.json +44 -36
  67. package/skills/data-api-interface/SKILL.md +58 -8
@@ -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
 
@@ -140,10 +140,10 @@ List responses include pagination metadata. The response structure is:
140
140
 
141
141
  ### Query Parameters
142
142
 
143
- | Parameter | Default | Description |
144
- | --------- | ------- | ----------- |
145
- | `limit` | `10` | Maximum number of records to return |
146
- | `offset` | `0` | Number of records to skip |
143
+ | Parameter | Default | Description |
144
+ | --------- | ------- | ----------------------------------- |
145
+ | `limit` | `10` | Maximum number of records to return |
146
+ | `offset` | `0` | Number of records to skip |
147
147
 
148
148
  ### Example
149
149
 
@@ -214,9 +214,9 @@ class UserAPI extends DataController(
214
214
 
215
215
  ### Sort Query Parameters
216
216
 
217
- | Parameter | Description |
218
- | --------- | ----------- |
219
- | `sortKey` | The field name to sort by. Must be decorated with `@Sortable`. |
217
+ | Parameter | Description |
218
+ | --------------- | ------------------------------------------------------------------ |
219
+ | `sortKey` | The field name to sort by. Must be decorated with `@Sortable`. |
220
220
  | `sortDirection` | `"asc"` (ascending) or `"desc"` (descending). Defaults to `"asc"`. |
221
221
 
222
222
  ### The `noIndex` Option
@@ -90,13 +90,13 @@ A GET request to `/orders/get?id=order-123` returns the full user object instead
90
90
  @Foreign(table, index?, multi?, pluck?, schema?)
91
91
  ```
92
92
 
93
- | Parameter | Type | Required | Default | Description |
94
- | --------- | ---- | -------- | ------- | ----------- |
95
- | `table` | `Class<Table>` or `string` | Yes | - | The target table class or table name string |
96
- | `index` | `string` | No | `"_id"` | The index on the target table to match against |
97
- | `multi` | `boolean` | No | `false` | Set to `true` when the field holds an array of foreign keys |
98
- | `pluck` | `string[]` | No | All fields | List of fields to include from the referenced record |
99
- | `schema` | `string` | No | Controller schema | Name of the schema the target table is registered in, when it differs from the controller's schema |
93
+ | Parameter | Type | Required | Default | Description |
94
+ | --------- | -------------------------- | -------- | ----------------- | -------------------------------------------------------------------------------------------------- |
95
+ | `table` | `Class<Table>` or `string` | Yes | - | The target table class or table name string |
96
+ | `index` | `string` | No | `"_id"` | The index on the target table to match against |
97
+ | `multi` | `boolean` | No | `false` | Set to `true` when the field holds an array of foreign keys |
98
+ | `pluck` | `string[]` | No | All fields | List of fields to include from the referenced record |
99
+ | `schema` | `string` | No | Controller schema | Name of the schema the target table is registered in, when it differs from the controller's schema |
100
100
 
101
101
  ## Table Reference Methods
102
102
 
package/docs/8.filters.md CHANGED
@@ -52,14 +52,14 @@ When no operator is specified, `eq` (equal) is used by default.
52
52
 
53
53
  ### Comparison Operators
54
54
 
55
- | Operator | Description |
56
- | -------- | ----------- |
57
- | `eq` | Equal to (default) |
58
- | `ne` | Not equal to |
59
- | `gt` | Greater than |
60
- | `ge` | Greater than or equal to |
61
- | `lt` | Less than |
62
- | `le` | Less than or equal to |
55
+ | Operator | Description |
56
+ | -------- | ------------------------ |
57
+ | `eq` | Equal to (default) |
58
+ | `ne` | Not equal to |
59
+ | `gt` | Greater than |
60
+ | `ge` | Greater than or equal to |
61
+ | `lt` | Less than |
62
+ | `le` | Less than or equal to |
63
63
 
64
64
  ### Examples
65
65
 
@@ -98,14 +98,14 @@ type FilterFunction<T, U> = (
98
98
  ) => ValueProxyOrValue<boolean>;
99
99
  ```
100
100
 
101
- | Parameter | Description |
102
- | --------- | ----------- |
103
- | `context` | The request context, extended with `this` referencing the controller instance |
104
- | `proxy` | A `ValueProxy` representing the field value with modifier transformations applied (e.g., decrypted, localized) |
105
- | `key` | The field name being filtered |
106
- | `value` | The filter value from the query parameter |
107
- | `mode` | The comparison operator from the query parameter |
108
- | `row` | The full database row as a `ValueProxy` |
101
+ | Parameter | Description |
102
+ | --------- | -------------------------------------------------------------------------------------------------------------- |
103
+ | `context` | The request context, extended with `this` referencing the controller instance |
104
+ | `proxy` | A `ValueProxy` representing the field value with modifier transformations applied (e.g., decrypted, localized) |
105
+ | `key` | The field name being filtered |
106
+ | `value` | The filter value from the query parameter |
107
+ | `mode` | The comparison operator from the query parameter |
108
+ | `row` | The full database row as a `ValueProxy` |
109
109
 
110
110
  ### Example: Pattern Matching Filter
111
111
 
@@ -20,8 +20,8 @@ import { ModifierKey } from "@antelopejs/interface-data-api/metadata";
20
20
  @ModifierKey(modifierClass: typeof ContainerModifier<any>)
21
21
  ```
22
22
 
23
- | Parameter | Description |
24
- | --------- | ----------- |
23
+ | Parameter | Description |
24
+ | --------------- | ----------------------------------------------------------------------------- |
25
25
  | `modifierClass` | The modifier class that requires a runtime key (e.g., `LocalizationModifier`) |
26
26
 
27
27
  ### Example: Localized Content
package/package.json CHANGED
@@ -1,26 +1,35 @@
1
1
  {
2
2
  "name": "@antelopejs/interface-data-api",
3
- "version": "0.1.9",
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",
7
- "interface",
7
+ "crud",
8
8
  "data-api",
9
- "crud"
9
+ "interface"
10
10
  ],
11
+ "license": "Apache-2.0",
11
12
  "repository": {
12
13
  "type": "git",
13
14
  "url": "git+https://github.com/AntelopeJS/interface-data-api.git"
14
15
  },
15
- "license": "Apache-2.0",
16
- "packageManager": "pnpm@10.6.5",
17
- "main": "dist/index.js",
18
- "types": "dist/index.d.ts",
19
16
  "files": [
20
17
  "dist",
21
18
  "docs",
22
19
  "skills"
23
20
  ],
21
+ "main": "dist/index.js",
22
+ "types": "dist/index.d.ts",
23
+ "typesVersions": {
24
+ "*": {
25
+ "components": [
26
+ "dist/components.d.ts"
27
+ ],
28
+ "metadata": [
29
+ "dist/metadata.d.ts"
30
+ ]
31
+ }
32
+ },
24
33
  "exports": {
25
34
  ".": {
26
35
  "types": "./dist/index.d.ts",
@@ -32,54 +41,53 @@
32
41
  },
33
42
  "./package.json": "./package.json"
34
43
  },
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "provenance": true
47
+ },
35
48
  "scripts": {
36
49
  "build": "rimraf dist && tsc",
37
- "format": "biome format --write .",
38
- "lint:fix": "biome check --write .",
39
- "lint": "biome check .",
50
+ "format": "oxfmt .",
51
+ "lint:fix": "oxlint --fix",
52
+ "lint": "oxlint",
40
53
  "prepack": "pnpm run build",
41
54
  "release": "pnpm run lint && pnpm run prepack && release-it",
42
- "test": "pnpm run build && ajs module test ."
55
+ "test": "pnpm run build && ajs module test .",
56
+ "format:check": "oxfmt --check .",
57
+ "knip": "knip"
43
58
  },
44
59
  "devDependencies": {
45
- "@antelopejs/interface-api": "^0.0.5",
46
- "@antelopejs/interface-api-util": "^0.1.1",
47
- "@antelopejs/interface-core": "^0.0.5",
48
- "@antelopejs/interface-database": "^0.1.3",
49
- "@antelopejs/interface-database-decorators": "^0.1.4",
50
- "@biomejs/biome": "2.3.2",
60
+ "@antelopejs/core": "^1.13.4",
61
+ "@antelopejs/interface-api": "^0.0.13",
62
+ "@antelopejs/interface-api-util": "^0.1.2",
63
+ "@antelopejs/interface-core": "^0.1.1",
64
+ "@antelopejs/interface-database": "^0.1.7",
65
+ "@antelopejs/interface-database-decorators": "^0.1.6",
66
+ "@antelopejs/tooling-configs": "^0.0.6",
51
67
  "@types/chai": "^4.3.20",
52
68
  "@types/mocha": "^10.0.10",
53
69
  "@types/node": "^22.19.15",
54
70
  "@types/semver": "^7.7.1",
55
71
  "chai": "^4.5.0",
72
+ "eslint-plugin-perfectionist": "^5.11.0",
73
+ "knip": "^6.34.0",
56
74
  "mongodb-memory-server-core": "^10.4.3",
75
+ "oxfmt": "^0.66.0",
76
+ "oxlint": "1.81.0",
77
+ "oxlint-tsgolint": "^7.0.2001",
57
78
  "release-it": "^19.2.4",
58
79
  "release-it-changelogen": "^0.1.0",
59
80
  "rimraf": "^6.1.3",
60
81
  "typescript": "^5.9.3"
61
82
  },
62
83
  "peerDependencies": {
63
- "@antelopejs/interface-api": ">=0.0.3 <1.0.0",
64
- "@antelopejs/interface-api-util": ">=0.0.4 <1.0.0",
65
- "@antelopejs/interface-core": ">=0.0.3 <1.0.0",
66
- "@antelopejs/interface-database": ">=0.1.2 <1.0.0",
67
- "@antelopejs/interface-database-decorators": ">=0.1.1 <1.0.0"
68
- },
69
- "publishConfig": {
70
- "access": "public",
71
- "provenance": true
72
- },
73
- "typesVersions": {
74
- "*": {
75
- "components": [
76
- "dist/components.d.ts"
77
- ],
78
- "metadata": [
79
- "dist/metadata.d.ts"
80
- ]
81
- }
84
+ "@antelopejs/interface-api": ">=0.0.13 <1.0.0",
85
+ "@antelopejs/interface-api-util": ">=0.1.2 <1.0.0",
86
+ "@antelopejs/interface-core": ">=0.1.1 <1.0.0",
87
+ "@antelopejs/interface-database": ">=0.1.7 <1.0.0",
88
+ "@antelopejs/interface-database-decorators": ">=0.1.6 <1.0.0"
82
89
  },
90
+ "packageManager": "pnpm@10.6.5",
83
91
  "antelopeJs": {
84
92
  "standalone": true,
85
93
  "test": "src/antelope.test.ts",
@@ -17,9 +17,32 @@ your module must also import those peer interfaces.
17
17
  ## Imports
18
18
 
19
19
  ```typescript
20
- import { DataController, DefaultRoutes, RegisterDataController, GetDataControllerMeta } from "@antelopejs/interface-data-api";
21
- import { Access, AccessMode, Listable, Mandatory, Optional, Sortable, Filter, Foreign, Joined, Computed, Validator, ModelReference, ModifierKey } from "@antelopejs/interface-data-api/metadata";
22
- import { Parameters, Query, Validation } from "@antelopejs/interface-data-api/components";
20
+ import {
21
+ DataController,
22
+ DefaultRoutes,
23
+ RegisterDataController,
24
+ GetDataControllerMeta,
25
+ } from "@antelopejs/interface-data-api";
26
+ import {
27
+ Access,
28
+ AccessMode,
29
+ Listable,
30
+ Mandatory,
31
+ Optional,
32
+ Sortable,
33
+ Filter,
34
+ Foreign,
35
+ Joined,
36
+ Computed,
37
+ Validator,
38
+ ModelReference,
39
+ ModifierKey,
40
+ } from "@antelopejs/interface-data-api/metadata";
41
+ import {
42
+ Parameters,
43
+ Query,
44
+ Validation,
45
+ } from "@antelopejs/interface-data-api/components";
23
46
  ```
24
47
 
25
48
  `/components` is only needed for custom route callbacks (query building, validation helpers).
@@ -28,9 +51,26 @@ import { Parameters, Query, Validation } from "@antelopejs/interface-data-api/co
28
51
 
29
52
  ```typescript
30
53
  import { Controller } from "@antelopejs/interface-api";
31
- import { DataController, DefaultRoutes, RegisterDataController } from "@antelopejs/interface-data-api";
32
- import { Access, AccessMode, Listable, Mandatory, ModelReference, Sortable } from "@antelopejs/interface-data-api/metadata";
33
- import { BasicDataModel, Field, Model, RegisterTable, Table } from "@antelopejs/interface-database-decorators";
54
+ import {
55
+ DataController,
56
+ DefaultRoutes,
57
+ RegisterDataController,
58
+ } from "@antelopejs/interface-data-api";
59
+ import {
60
+ Access,
61
+ AccessMode,
62
+ Listable,
63
+ Mandatory,
64
+ ModelReference,
65
+ Sortable,
66
+ } from "@antelopejs/interface-data-api/metadata";
67
+ import {
68
+ BasicDataModel,
69
+ Field,
70
+ Model,
71
+ RegisterTable,
72
+ Table,
73
+ } from "@antelopejs/interface-database-decorators";
34
74
 
35
75
  @RegisterTable("tasks", "default")
36
76
  class Task extends Table {
@@ -43,7 +83,11 @@ class Task extends Table {
43
83
  class TaskModel extends BasicDataModel(Task, "tasks") {}
44
84
 
45
85
  @RegisterDataController()
46
- class TaskAPI extends DataController(Task, DefaultRoutes.All, Controller("/tasks")) {
86
+ class TaskAPI extends DataController(
87
+ Task,
88
+ DefaultRoutes.All,
89
+ Controller("/tasks"),
90
+ ) {
47
91
  @ModelReference()
48
92
  @Model(TaskModel, "my-database")
49
93
  declare taskModel: TaskModel;
@@ -67,7 +111,8 @@ This exposes `GET /tasks/get?id=`, `GET /tasks/list`, `POST /tasks/new`, `PUT /t
67
111
 
68
112
  - `@RegisterDataController()` is mandatory: it wires field decorators, parameter providers, and
69
113
  routes. A `@ModelReference()` property holding a `@Model` instance is required too (`Query.GetModel`
70
- throws 500 without it).
114
+ throws 500 without it). A controller derived from another data controller inherits it unless it
115
+ declares its own.
71
116
  - Fields with no decorator are invisible to the API — neither returned nor writable; visibility
72
117
  requires `@Access`. `@Optional()` only registers a field as not-mandatory (it does not expose it).
73
118
  - `list` responds `{ results, total, offset, limit }`. `limit` defaults to 10 and is capped at 100
@@ -83,6 +128,11 @@ This exposes `GET /tasks/get?id=`, `GET /tasks/list`, `POST /tasks/new`, `PUT /t
83
128
  of `DefaultRoutes.All`; use `DefaultRoutes.WithOptions(route, options, endpoint)` to rename an
84
129
  endpoint or preset parameter options.
85
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.
86
136
 
87
137
  ## Deeper reference
88
138