@antelopejs/interface-dms-builder 0.3.1 → 0.4.1

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.
@@ -1,18 +1,22 @@
1
- # Resources
1
+ ---
2
+ title: "Resources"
3
+ description: "The operations that create, read, change and delete a DMS resource, the database table and DataAPI controller behind a feature, and the types they take."
4
+ mode: reference
5
+ ---
2
6
 
3
7
  ## Overview
4
8
 
5
- A resource is the data half of a DMS feature: a database `Table`, a `DataController` exposing it over HTTP, and the model that binds them. `CreateResource` writes all of it into a `<name>/` folder — `database.ts`, `data-api.ts` and an `index.ts` barrel — and wires the nearest barrel above so the project keeps a relative import chain to `src/index.ts`.
9
+ A resource is the data half of a DMS feature. It is a database `Table`, a `DataController` that serves the table over HTTP, and the model that binds them. `CreateResource` writes all three into a `<name>/` folder as `database.ts`, `data-api.ts` and an `index.ts` barrel. It then wires the nearest barrel above that folder, so the project keeps a chain of relative imports up to `src/index.ts`.
6
10
 
7
- You describe a resource in semantic terms: each field carries a DataType and a set of aspects such as *searchable* or *sortable*, and the engine maps those to the paired decorator stacks on the table and the controller. You never write a decorator yourself, and you never name one in a request.
11
+ A request describes a resource in semantic terms. Each field carries a DataType and a set of aspects such as `searchable` or `sortable`. The engine maps those aspects to the paired decorator stacks on the table and on the controller. A request never names a decorator.
8
12
 
9
- A resource is also what a controller-leading block points at: `AddBlock` with `controller: "<ref>"` emits the resource's DataAPI class as the block's leading argument, as described in [Page Operations](./4.page-operations.md).
13
+ A block that leads with a controller points at a resource. `AddBlock` with `controller: "<ref>"` emits the resource's DataAPI class as the block's first argument, as [Page operations](./4.page-operations.md) describes.
10
14
 
11
- ## Address a Resource and a Field
15
+ ## Address a resource and a field
12
16
 
13
- `ResourceRef` is the resource's name in kebab form — `"product"`, `"order-line"`. It is what `CreateResource` returns, what `ListResources` reports, and what every other resource operation takes.
17
+ `ResourceRef` is the resource's name in kebab case, such as `"product"` or `"order-line"`. `CreateResource` returns it, `ListResources` reports it, and every other resource operation takes it.
14
18
 
15
- `FieldPath` addresses one field within a resource: `<resourceRef>#<fieldName>`, e.g. `product#price`. `AddField` returns the path it created.
19
+ `FieldPath` addresses one field of a resource as `<resourceRef>#<fieldName>`, for example `product#price`. `AddField` returns the path it created.
16
20
 
17
21
  ```typescript
18
22
  import type {
@@ -24,7 +28,9 @@ const resource: ResourceRef = "product";
24
28
  const field: FieldPath = "product#price";
25
29
  ```
26
30
 
27
- ## Create a Resource
31
+ ## Create a resource
32
+
33
+ `CreateResource(input)` returns an `OpResult<{ ref: ResourceRef; files: ResourceFiles }>`.
28
34
 
29
35
  ```typescript
30
36
  import { CreateResource } from "@antelopejs/interface-dms-builder";
@@ -54,54 +60,63 @@ if (result.ok) {
54
60
  }
55
61
  ```
56
62
 
57
- `CreateResourceInput`:
63
+ `CreateResourceInput` has these options:
58
64
 
59
65
  | Option | Type | Description |
60
66
  | --- | --- | --- |
61
- | `name` | `string` | The resource name. Required, and it must contain at least one alphanumeric character. A duplicate fails with `duplicate_name`. |
67
+ | `name` | `string` | The resource name. Required. It must contain at least one alphanumeric character, or the operation fails with `invalid_config`. A name already in use, or a target folder that already exists, fails with `duplicate_name`. |
62
68
  | `fields` | `FieldSpec[]` | The resource's fields. Required. |
63
69
  | `displayName` | `string` | A human-readable name for the resource. |
64
70
  | `schema` | `string` | The database schema. Defaults to the DMS core schema, `CORE_SCHEMA_NAME` (`dms-core`). |
65
- | `seeds` | `Record<string, unknown>[]` | Rows to seed as a fixture. Each row must supply its own `_id`. |
66
- | `location` | `ResourceLocation` | Where the generated folder goes. Omit for `src/<name>/`. |
67
- | `routes` | `ResourceRoute[]` | Which DataAPI routes to expose. Omit for the full set. Must be non-empty when provided. |
71
+ | `seeds` | `ConfigObject[]` | Rows to seed as a `@Fixture`. Each row must supply its own `_id`. |
72
+ | `location` | `ResourceLocation` | The folder the generated `<name>/` folder goes in. Omit it for `src/<name>/`. |
73
+ | `routes` | `ResourceRoute[]` | The DataAPI routes to expose. Omit it for the full set. It must be non-empty when present. |
74
+ | `module` | `string` | The project module to write into, by its id in the project configuration. Unset, the operation writes into the project's own module. The module must be a local module with an `src/index.ts`, and what the operation writes there can only use declarations from that module. |
75
+
76
+ The engine derives every generated name from `name`. For `product` it writes a `Product` table class, a `ProductModel`, a `productDataAPI` controller on the route `/api/product`, and a `products` table.
68
77
 
69
- The names the engine derives from `name` are predictable: `product` yields a `Product` table class, a `ProductModel`, a `productDataAPI` controller on the route `/api/product`, and a `products` table.
78
+ A location inside a module that mirrors an external database fails with `invalid_config`, because no table is created there. Map the relations of such a module with `MapResources`, as [External databases](./11.external-databases.md) describes.
70
79
 
71
- ## Describe a Field
80
+ ## Describe a field
72
81
 
73
- `FieldSpec` is a `FieldAspects` plus the field's `name`. The name is immutable — renaming a field is `RemoveField` followed by `AddField`.
82
+ `FieldSpec` is a `FieldAspects` plus the field's `name`. The name never changes. To rename a field, call `RemoveField`, then `AddField`.
74
83
 
75
84
  | Option | Type | Description |
76
85
  | --- | --- | --- |
77
- | `name` | `string` | The field name. Rejected with `invalid_config` when it is one of the catalog's `reservedFieldNames`. |
78
- | `dataType` | `DataTypeValue` | The field's DataType, e.g. `{ $dataType: "string" }`. Required. |
79
- | `label` | `string` | Column header label. Defaults to the field name. |
80
- | `listable` | `boolean` | Selected and listed by the DataAPI. Default `true`. |
81
- | `searchable` | `boolean` | Included in full-text search. |
82
- | `sortable` | `boolean` | Sortable in the table view. |
83
- | `filterable` | `boolean` | Column-filterable in the table view. |
84
- | `access` | `"read" \| "readwrite"` | Field access mode. Default `"readwrite"`. |
85
- | `required` | `boolean` | Whether the field is required. Default `false`, which makes the field optional. |
86
- | `indexed` | `boolean` | Whether the table column is indexed. |
87
- | `order` | `number` | Display order of the column. |
88
-
89
- `dataType` takes a `DataTypeValue` sentinel, not a bare string; the ids come from the catalog's `dataTypes`. See [Value Encodings](./7.values.md) and [Catalog](./3.catalog.md).
90
-
91
- ## Choose the Exposed Routes
92
-
93
- `ResourceRoute` names one group of DataAPI endpoints. Passing a subset lets a resource serve only what it needs — a singleton edit form wants `["get", "edit"]` and nothing else. An omitted route's HTTP endpoint is simply not registered.
86
+ | `name` | `string` | The field name. A name in the catalog's `reservedFieldNames` fails with `invalid_config`. |
87
+ | `dataType` | `DataTypeValue` | The field's DataType, for example `{ $dataType: "string" }`. Required. |
88
+ | `label` | `string` | The column header label. Defaults to the field name. |
89
+ | `listable` | `boolean` | The list route selects and lists the field (`@Listable`). Default `true`. |
90
+ | `selectable` | `boolean` | The select route offers the field to relation pickers (`@Select`). |
91
+ | `searchable` | `boolean` | The field is part of full-text search. |
92
+ | `sortable` | `boolean` | The table view can sort by the field. |
93
+ | `filterable` | `boolean` | The table view can filter the field's column. |
94
+ | `access` | `"read" \| "readwrite"` | The field's access mode. Default `"readwrite"`. |
95
+ | `required` | `boolean` | Whether the field is required. Default `false`, which writes `@Optional()`. |
96
+ | `indexed` | `boolean` | Whether the table indexes the column. When a field is created, the default is `true` for `relation`, `cascader_relation` and `tree` fields and `false` for the others. |
97
+ | `order` | `number` | The display order of the column. |
98
+ | `exported` | `boolean` | The resource's CSV export includes the field (`@Exported`). |
99
+ | `archiveField` | `boolean` | The field is the boolean column that archiving sets (`@ArchiveField`). |
100
+ | `mandatory` | `string[]` | The routes the field is mandatory on, for example `["new", "edit"]` (`@Mandatory`). |
101
+ | `dbField` | `{ field: string; ts: string; optional?: boolean; decorators?: string[] }` | How the table declares the column. It holds the `@Field(...)` argument, the TypeScript type, and the table decorators the builder does not model, such as `@Localized()`. The builder keeps the declaration of a field it did not write, instead of narrowing it to the `string` fallback. |
102
+ | `columnExtras` | `ConfigObject` | The `@Column` options the builder does not model, such as `defaultValue` or `description`. The builder reads them back and writes them again unchanged. |
103
+
104
+ `dataType` takes a `DataTypeValue` sentinel, not a bare string. The ids come from the catalog's `dataTypes`. See [Value encodings](./7.values.md) and [Catalog](./3.catalog.md).
105
+
106
+ ## Choose the exposed routes
107
+
108
+ `ResourceRoute` names one group of DataAPI endpoints. A subset serves only what the resource needs. A singleton edit form, for example, needs `["get", "edit"]` and nothing else. The DataAPI does not register the endpoint of a route you leave out.
94
109
 
95
110
  | Route | What it exposes |
96
111
  | --- | --- |
97
- | `list` | The paginated listing (`list` + `count`). |
112
+ | `list` | The paginated listing (`list`, `count`, `countBatch` and `summary`). |
98
113
  | `get` | Read a single row. |
99
- | `create` | Create a row. |
114
+ | `create` | Create a row (`new`). |
100
115
  | `edit` | Update a row. |
101
116
  | `delete` | Delete a row. |
102
- | `select` | Option lists for relation pickers that reference this resource. |
103
- | `archive` | Soft delete and undo (`archive` + `restore`). |
104
- | `export` | The export endpoints (`exportStart` / `exportStatus` / `exportDownload`). |
117
+ | `select` | Option lists for the relation pickers that reference this resource. |
118
+ | `archive` | Soft delete and undo (`archive` and `restore`). |
119
+ | `export` | The export endpoints (`exportStart`, `exportStatus` and `exportDownload`). |
105
120
 
106
121
  ```typescript
107
122
  import { CreateResource } from "@antelopejs/interface-dms-builder";
@@ -115,15 +130,23 @@ await CreateResource({
115
130
 
116
131
  An empty array, or a name that is not one of the eight, fails with `invalid_config`.
117
132
 
118
- ## Place the Generated Files
133
+ `ConfigureResource(ref, patch, opts?)` changes the routes of an existing resource. `patch.routes` replaces the current set, so pass the whole list. A patch without `routes` changes nothing. An empty array fails with `invalid_config`. A controller with no route map to patch fails with `unsupported`.
119
134
 
120
- `ResourceLocation` decides which folder the `<name>/` folder lands in. The nearest ancestor barrel to the resolved folder is the one that gets wired, with the root barrel as the fallback.
135
+ ```typescript
136
+ import { ConfigureResource } from "@antelopejs/interface-dms-builder";
121
137
 
122
- | Form | Where the resource lands |
138
+ await ConfigureResource("site-settings", { routes: ["get", "edit", "list"] });
139
+ ```
140
+
141
+ ## Place the generated files
142
+
143
+ `ResourceLocation` sets the folder that the `<name>/` folder goes in. The engine wires the barrel nearest to that folder, and falls back to the root barrel.
144
+
145
+ | Form | Where the resource goes |
123
146
  | --- | --- |
124
- | omitted | `src/<name>/` |
125
- | `"reporting/data"` | `src/reporting/data/<name>/` — an explicit folder relative to the src root, which must stay inside the src tree. |
126
- | `{ category: "pages.reports" }` | The source folder of that category, mirroring page placement. A root category falls back to the src root. |
147
+ | Omitted | `src/<name>/`. |
148
+ | `"reporting/data"` | `src/reporting/data/<name>/`. The string is a folder relative to the src root and must stay inside the src tree. |
149
+ | `{ category: "pages.reports" }` | The source folder of that category, as for a page. A root category falls back to the src root. |
127
150
  | `{ page: "/revenue" }` | Inside that page's feature folder, so the page imports `./<name>/data-api`. |
128
151
 
129
152
  ```typescript
@@ -136,11 +159,11 @@ await CreateResource({
136
159
  });
137
160
  ```
138
161
 
139
- A `dir` string that escapes the src tree, an unknown category and an unknown page all fail with `invalid_config`.
162
+ A string that leaves the src tree, an unknown category and an unknown page each fail with `invalid_config`.
140
163
 
141
- ## Add, Configure and Remove a Field
164
+ ## Add, configure and remove a field
142
165
 
143
- `AddField(ref, field, opts?)` appends a field to both generated files and returns its `FieldPath`.
166
+ `AddField(ref, field, opts?)` adds a field to both generated files and returns its `FieldPath`.
144
167
 
145
168
  ```typescript
146
169
  import { AddField } from "@antelopejs/interface-dms-builder";
@@ -157,9 +180,9 @@ if (result.ok) {
157
180
  }
158
181
  ```
159
182
 
160
- A name already present on the table or the controller fails with `duplicate_name`; a reserved name fails with `invalid_config`.
183
+ A name that the table or the controller already declares fails with `duplicate_name`. A reserved name fails with `invalid_config`. So does a resource that mirrors an external database, whose columns belong to that database.
161
184
 
162
- `ConfigureField(path, patch, opts?)` reads the field's current aspects, merges your `Partial<FieldAspects>` over them, and rewrites both declarations from the result. Aspects you leave out keep their current value.
185
+ `ConfigureField(path, patch, opts?)` reads the field's current aspects, merges your `Partial<FieldAspects>` over them, and writes the field again from the result. An aspect you leave out keeps its current value. The operation rewrites the table file only when the patch sets `dataType` or `indexed`.
163
186
 
164
187
  ```typescript
165
188
  import { ConfigureField } from "@antelopejs/interface-dms-builder";
@@ -170,9 +193,9 @@ await ConfigureField("product#price", {
170
193
  });
171
194
  ```
172
195
 
173
- A field whose decorator stack the builder could not reverse — a hand-edited one, or one carrying a decorator the builder does not know — is opaque, and configuring it fails with `opaque_target`.
196
+ A field whose decorator stack the builder cannot read back is opaque. A hand-edited field and a field with a decorator the builder does not know are two examples. Configuring an opaque field fails with `opaque_target`.
174
197
 
175
- `RemoveField(path, opts?)` drops the field from both files.
198
+ `RemoveField(path, opts?)` removes the field from both files.
176
199
 
177
200
  ```typescript
178
201
  import { RemoveField } from "@antelopejs/interface-dms-builder";
@@ -180,11 +203,11 @@ import { RemoveField } from "@antelopejs/interface-dms-builder";
180
203
  await RemoveField("product#sku");
181
204
  ```
182
205
 
183
- All three take a `MutationOpts`, whose `expectedVersion` is checked against the resource's version — a hash of both generated files.
206
+ All three take a `MutationOpts`. Its `expectedVersion` is compared with the resource's version, a hash of both generated files, and a mismatch fails with `stale`. A path whose resource or field does not exist fails with `not_found`.
184
207
 
185
- ## List Resources
208
+ ## List resources
186
209
 
187
- `ListResources()` returns a `ResourceSummary[]` directly, without an `OpResult` wrapper.
210
+ `ListResources()` returns a `ResourceSummary[]` directly, without an `OpResult` around it.
188
211
 
189
212
  | Field | Type | Description |
190
213
  | --- | --- | --- |
@@ -192,8 +215,9 @@ All three take a `MutationOpts`, whose `expectedVersion` is checked against the
192
215
  | `className` | `string` | The generated table class name. |
193
216
  | `tableName` | `string` | The database table name. |
194
217
  | `route` | `string` | The DataAPI's base route. |
195
- | `fieldCount` | `number` | How many fields the resource has. |
218
+ | `fieldCount` | `number` | The number of fields the resource has. |
196
219
  | `files` | `{ database: string; dataApi: string }` | The two source files. |
220
+ | `sourceModule` | `string` | The project module the resource lives in. Present only when that module is not the project's own. |
197
221
 
198
222
  ```typescript
199
223
  import { ListResources } from "@antelopejs/interface-dms-builder";
@@ -202,9 +226,9 @@ const resources = await ListResources();
202
226
  const refs = resources.map((resource) => resource.ref);
203
227
  ```
204
228
 
205
- ## Read a Resource's Structure
229
+ ## Read a resource's structure
206
230
 
207
- `GetResourceStructure(ref)` returns an `OpResult<ResourceStructure>`. Like the other reads, its `changes` array is empty.
231
+ `GetResourceStructure(ref)` returns an `OpResult<ResourceStructure>`. Like the other reads, it returns an empty `changes` array. An unknown ref fails with `not_found`.
208
232
 
209
233
  ```typescript
210
234
  import { GetResourceStructure } from "@antelopejs/interface-dms-builder";
@@ -215,7 +239,7 @@ if (result.ok) {
215
239
  }
216
240
  ```
217
241
 
218
- `ResourceStructure`:
242
+ `ResourceStructure` has these fields:
219
243
 
220
244
  | Field | Type | Description |
221
245
  | --- | --- | --- |
@@ -225,25 +249,25 @@ if (result.ok) {
225
249
  | `route` | `string` | The DataAPI's base route. |
226
250
  | `schema` | `string` | The database schema the table is registered in. |
227
251
  | `files` | `ResourceFiles` | The resource's three files. |
228
- | `fields` | `ResourceFieldStructure[]` | The fields, in declaration order. |
229
- | `routes` | `ResourceRoute[]` | The exposed routes when the resource serves a subset; omitted when it serves the full set. Best-effort: absent for a hand-edited route map the engine could not map back to semantic names. |
252
+ | `fields` | `ResourceFieldStructure[]` | The fields, in declaration order. The `_id` and `model` members every resource has are left out. |
253
+ | `routes` | `ResourceRoute[]` | The exposed routes when the resource serves a subset. Absent when it serves the full set. Also absent for a hand-edited route map that the engine cannot map back to route names. |
230
254
  | `version` | `string` | A content hash of both generated files. |
231
255
 
232
- `ResourceFiles` names the three paths a resource is composed of: `database`, `dataApi` and `index`.
256
+ `ResourceFiles` holds the paths of a resource's three files, as `database`, `dataApi` and `index`.
233
257
 
234
- `ResourceFieldStructure` is a `Partial<FieldAspects>` plus:
258
+ `ResourceFieldStructure` is a `Partial<FieldAspects>` plus these fields:
235
259
 
236
260
  | Field | Type | Description |
237
261
  | --- | --- | --- |
238
262
  | `name` | `string` | The field name. Always present. |
239
- | `opaque` | `boolean` | `true` when the field's decorator stack could not be reversed. |
240
- | `opaqueReason` | `string` | Why it is opaque, e.g. an unsupported decorator or a missing `@Column` stack. Set when `opaque === true`. |
263
+ | `opaque` | `boolean` | `true` when the builder cannot read the field's decorator stack back. |
264
+ | `opaqueReason` | `string` | Why the field is opaque, such as an unsupported decorator or a missing `@Column` stack. Set when `opaque` is `true`. |
241
265
 
242
- A cleanly-reversed field carries its aspects; an opaque one carries only `name` and `opaqueReason`. Read `opaque` before offering a field for editing.
266
+ A field the builder reads back carries its aspects. An opaque field carries only `name`, `opaque` and `opaqueReason`. Check `opaque` before you offer a field for editing.
243
267
 
244
- ## Delete a Resource
268
+ ## Delete a resource
245
269
 
246
- `DeleteResource(ref, opts?)` deletes the three generated files, unwires every import of them, removes the folder if it is now empty, and — by default — clears the resource's database table so a later resource reusing the name re-seeds cleanly instead of inheriting stale documents.
270
+ `DeleteResource(ref, opts?)` deletes the three generated files and removes every import of the resource. It then removes the folder if the folder is empty. By default it also deletes every row of the resource's table, so a later resource with the same name seeds again from an empty table.
247
271
 
248
272
  ```typescript
249
273
  import { DeleteResource } from "@antelopejs/interface-dms-builder";
@@ -260,10 +284,12 @@ if (result.ok) {
260
284
 
261
285
  | Option | Type | Description |
262
286
  | --- | --- | --- |
263
- | `keepData` | `boolean` | Leave the resource's table data in place instead of deleting every row. |
287
+ | `keepData` | `boolean` | Leave the rows of the resource's table in place. |
264
288
  | `expectedVersion` | `string` | The `ResourceStructure.version` you last read. A mismatch fails with `stale`. |
265
289
 
266
- When the rows cannot be dropped, the operation still succeeds and attaches a `data_not_dropped` warning naming the reason — the source is gone, the data may not be.
290
+ While another resource imports this one, through a relation field for example, the operation fails with `referential_integrity`. The error's `blockedBy` lists those resources.
291
+
292
+ When the rows cannot be deleted, the operation still succeeds and adds a `data_not_dropped` warning with the reason. The files are gone and the rows may remain. A resource that mirrors an external database keeps its rows, and the operation adds a `data_kept` warning.
267
293
 
268
294
  ```typescript
269
295
  import { DeleteResource } from "@antelopejs/interface-dms-builder";
@@ -271,6 +297,6 @@ import { DeleteResource } from "@antelopejs/interface-dms-builder";
271
297
  await DeleteResource("product", { keepData: true });
272
298
  ```
273
299
 
274
- ## Next Steps
300
+ ## Next steps
275
301
 
276
- - [Queries](./6.queries.md) - Derive counts and aggregates from a resource and serve them from a page
302
+ - [Queries](./6.queries.md) shows how to derive counts and aggregates from a resource and serve them from a page.