@antelopejs/interface-dms-builder 0.3.1 → 0.4.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.
- package/README.md +17 -17
- package/dist/catalog.d.ts +7 -7
- package/dist/drafts.d.ts +10 -9
- package/dist/drafts.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/operation-map.d.ts +66 -0
- package/dist/operation-map.js +57 -0
- package/dist/operation-map.js.map +1 -0
- package/dist/operations.d.ts +6 -5
- package/dist/operations.js.map +1 -1
- package/dist/project.d.ts +3 -2
- package/dist/project.js.map +1 -1
- package/dist/queries.d.ts +12 -11
- package/dist/resources.d.ts +3 -3
- package/dist/results.d.ts +4 -4
- package/dist/structure.d.ts +5 -4
- package/dist/values.d.ts +14 -10
- package/docs/1.introduction.md +28 -22
- package/docs/10.a-chart-without-code.md +47 -71
- package/docs/11.external-databases.md +93 -70
- package/docs/2.addressing-and-structure.md +48 -40
- package/docs/3.catalog.md +105 -49
- package/docs/4.page-operations.md +74 -60
- package/docs/5.resources.md +97 -71
- package/docs/6.queries.md +94 -95
- package/docs/7.values.md +38 -32
- package/docs/8.results-and-errors.md +62 -45
- package/docs/9.drafts-and-preview.md +84 -48
- package/docs/images/captures/editor-selected.webp +0 -0
- package/docs/tsconfig.json +4 -0
- package/package.json +2 -2
- package/docs/images/editor-selected.png +0 -0
package/docs/5.resources.md
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
15
|
+
## Address a resource and a field
|
|
12
16
|
|
|
13
|
-
`ResourceRef` is the resource's name in kebab
|
|
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
|
|
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
|
|
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
|
|
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` | `
|
|
66
|
-
| `location` | `ResourceLocation` |
|
|
67
|
-
| `routes` | `ResourceRoute[]` |
|
|
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
|
-
|
|
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
|
|
80
|
+
## Describe a field
|
|
72
81
|
|
|
73
|
-
`FieldSpec` is a `FieldAspects` plus the field's `name`. The name
|
|
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.
|
|
78
|
-
| `dataType` | `DataTypeValue` | The field's DataType,
|
|
79
|
-
| `label` | `string` |
|
|
80
|
-
| `listable` | `boolean` |
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
|
|
89
|
-
`
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
`
|
|
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`
|
|
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`
|
|
104
|
-
| `export` | The export endpoints (`exportStart
|
|
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
|
-
|
|
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
|
-
|
|
135
|
+
```typescript
|
|
136
|
+
import { ConfigureResource } from "@antelopejs/interface-dms-builder";
|
|
121
137
|
|
|
122
|
-
|
|
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
|
-
|
|
|
125
|
-
| `"reporting/data"` | `src/reporting/data/<name
|
|
126
|
-
| `{ category: "pages.reports" }` | The source folder of that category,
|
|
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
|
|
162
|
+
A string that leaves the src tree, an unknown category and an unknown page each fail with `invalid_config`.
|
|
140
163
|
|
|
141
|
-
## Add,
|
|
164
|
+
## Add, configure and remove a field
|
|
142
165
|
|
|
143
|
-
`AddField(ref, field, opts?)`
|
|
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
|
|
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
|
|
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
|
|
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?)`
|
|
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
|
|
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
|
|
208
|
+
## List resources
|
|
186
209
|
|
|
187
|
-
`ListResources()` returns a `ResourceSummary[]` directly, without an `OpResult`
|
|
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` |
|
|
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
|
|
229
|
+
## Read a resource's structure
|
|
206
230
|
|
|
207
|
-
`GetResourceStructure(ref)` returns an `OpResult<ResourceStructure>`. Like the other reads,
|
|
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
|
|
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`
|
|
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
|
|
240
|
-
| `opaqueReason` | `string` | Why
|
|
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
|
|
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
|
|
268
|
+
## Delete a resource
|
|
245
269
|
|
|
246
|
-
`DeleteResource(ref, opts?)` deletes the three generated files
|
|
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
|
|
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
|
-
|
|
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
|
|
300
|
+
## Next steps
|
|
275
301
|
|
|
276
|
-
- [Queries](./6.queries.md)
|
|
302
|
+
- [Queries](./6.queries.md) shows how to derive counts and aggregates from a resource and serve them from a page.
|