api_binding_ui 3.0.0 → 3.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.
- package/README.md +118 -80
- package/SKILL.md +45 -45
- package/dist/app/{index-0n23yy5r.js → index-xww2x0kd.js} +138 -138
- package/dist/app/index.html +1 -1
- package/dist/cli/scan.d.ts +2 -0
- package/dist/cli.js +333 -207
- package/dist/codegen/write.d.ts +18 -0
- package/dist/framework/contract.d.ts +25 -1
- package/dist/framework/file.d.ts +6 -0
- package/dist/{index-jj8twdw9.js → index-gfw08965.js} +5 -2
- package/dist/{index-jj8twdw9.js.map → index-gfw08965.js.map} +3 -3
- package/dist/index.js +11 -4
- package/dist/index.js.map +3 -3
- package/dist/overlay.js +24 -0
- package/dist/server/handler.d.ts +25 -0
- package/dist/studio/BindingStudio.d.ts +3 -1
- package/dist/studio/index.js +8 -6
- package/dist/studio/index.js.map +3 -3
- package/dist/vite/plugin.d.ts +49 -0
- package/dist/vite.js +1113 -0
- package/package.json +7 -3
- package/spec/FRAMEWORK.md +178 -176
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api_binding_ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Design-time API binding: register screen components, bind them to documented APIs in a studio, and generate plain React Query hooks over an Orval client. Nothing ships in the app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
"import": "./dist/studio/index.js"
|
|
17
17
|
},
|
|
18
18
|
"./studio.css": "./dist/studio/index.css",
|
|
19
|
-
"./package.json": "./package.json"
|
|
19
|
+
"./package.json": "./package.json",
|
|
20
|
+
"./vite": {
|
|
21
|
+
"types": "./dist/vite/plugin.d.ts",
|
|
22
|
+
"import": "./dist/vite.js"
|
|
23
|
+
}
|
|
20
24
|
},
|
|
21
25
|
"files": [
|
|
22
26
|
"dist",
|
|
@@ -30,7 +34,7 @@
|
|
|
30
34
|
"scripts": {
|
|
31
35
|
"dev": "bun run build:app && node dist/cli.js studio src/dev/sample/bindings.json",
|
|
32
36
|
"build:app": "rm -rf dist/app && bun build ./index.html --outdir dist/app --minify",
|
|
33
|
-
"build": "rm -rf dist && bun build src/index.ts src/studio/index.ts --outdir dist --root src --target browser --splitting --sourcemap=linked --external react --external react-dom --external react/jsx-runtime --external yaml && bun build src/cli/cli.ts --outfile dist/cli.js --target node --external yaml --banner '#!/usr/bin/env node' && bun run build:app && node ./node_modules/typescript/bin/tsc -p tsconfig.build.json",
|
|
37
|
+
"build": "rm -rf dist && bun build src/index.ts src/studio/index.ts --outdir dist --root src --target browser --splitting --sourcemap=linked --external react --external react-dom --external react/jsx-runtime --external yaml && bun build src/cli/cli.ts --outfile dist/cli.js --target node --external yaml --banner '#!/usr/bin/env node' && bun build src/vite/plugin.ts --outfile dist/vite.js --target node --external yaml && bun build src/overlay/main.ts --outfile dist/overlay.js --target browser --minify && bun run build:app && node ./node_modules/typescript/bin/tsc -p tsconfig.build.json",
|
|
34
38
|
"typecheck": "node ./node_modules/typescript/bin/tsc --noEmit -p tsconfig.json",
|
|
35
39
|
"test": "bun test",
|
|
36
40
|
"prepublishOnly": "bun run typecheck && bun test && bun run build"
|
package/spec/FRAMEWORK.md
CHANGED
|
@@ -1,176 +1,178 @@
|
|
|
1
|
-
# API Binding Framework — v3
|
|
2
|
-
|
|
3
|
-
Normative types: [`contract.ts`](../src/framework/contract.ts).
|
|
4
|
-
|
|
5
|
-
The framework connects **UI components** to **documented API operations** at design time and turns the result into **plain code**.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. Principles
|
|
10
|
-
|
|
11
|
-
1. **Nothing concrete in the framework.** Component kinds, products, operations and fields arrive as data: kinds and the registry in the bindings file, operations from the OpenAPI docs.
|
|
12
|
-
2. **The API docs are the only source of operations.** No local hooks, hardcoded option lists or free-text overrides.
|
|
13
|
-
3. **UI first.** The unit of work is a screen and its components; APIs attach to component slots.
|
|
14
|
-
4. **
|
|
15
|
-
5. **No trace at run time.** The generator writes plain hooks; the app never imports this package.
|
|
16
|
-
6. **Deterministic.** The same file and docs always produce the same code. Generated files carry an `@generated` mark and are never edited by hand.
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## 2. The pieces
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
OpenAPI docs ──► Orval ──────────────► src/api/generated (typed client)
|
|
24
|
-
│
|
|
25
|
-
└──► api-binding studio ◄──► bindings.json ──► api-binding generate ──► src/api/screens (one hook per screen)
|
|
26
|
-
│
|
|
27
|
-
app components ── use<Screen>Api(state) ┘
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
| Piece | What it is | Owned by |
|
|
31
|
-
|---|---|---|
|
|
32
|
-
| Kinds | A component kind's slots, with JSON Schema shapes | Design system, in `
|
|
33
|
-
| Screens and components | Every screen and component instance, by id | The app, in `
|
|
34
|
-
| Bindings | Which operation or value feeds each slot | Designers in the studio, reviewed by engineers |
|
|
35
|
-
| Orval client | Typed functions per operation | Generated by Orval from the same docs |
|
|
36
|
-
| Screen hooks | `use<Screen>Api(state)` per screen | Generated by `api-binding generate` |
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## 3. Ids
|
|
41
|
-
|
|
42
|
-
One namespace for screens and components, used everywhere: in `componentId` props, in the file and in expressions.
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
<product>.<screen>[.<sub-screen>…].<component>[.<part>…]
|
|
46
|
-
collections.worklist screen
|
|
47
|
-
collections.worklist.add-notes sub-screen (a dialog over the worklist)
|
|
48
|
-
collections.worklist.grid component on collections.worklist
|
|
49
|
-
collections.worklist.grid.column.customer-name
|
|
50
|
-
collections.worklist.grid.row.add-notes
|
|
51
|
-
collections.worklist.add-notes.notes-type component on collections.worklist.add-notes
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
- Segments are lowercase kebab-case (`[a-z0-9-]`), joined by dots: `^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$`.
|
|
55
|
-
- A component belongs to the **longest** registered screen id that prefixes it. A screen's parent is the longest other screen id that prefixes it. Navigation is built from this; there is no `path` or `parent` to maintain.
|
|
56
|
-
- A component id may not equal a screen id (it would capture that screen's components).
|
|
57
|
-
- Ids are stable: renaming one orphans its bindings. They never depend on labels.
|
|
58
|
-
- In the app, each component gets `componentId="<id>"`; design-system components render it as `data-component-id`.
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## 4. Kinds and slots
|
|
63
|
-
|
|
64
|
-
A kind declares slots with one of three **roles**:
|
|
65
|
-
|
|
66
|
-
| Role | Meaning | Bound to |
|
|
67
|
-
|---|---|---|
|
|
68
|
-
| `consume` | The component needs data | An operation (request + response selection) or an expression |
|
|
69
|
-
| `produce` | The component exposes data | Nothing; a source others read |
|
|
70
|
-
| `trigger` | The component emits an event | Ordered API call steps, or "no API" |
|
|
71
|
-
|
|
72
|
-
- `scope.parentSlot`: the slot is evaluated per item of the parent's slot (a cell in a row, an action on a row); `$item` is that item.
|
|
73
|
-
- `feeds`: produce slots of the same component offered as request sources (paging, sorting).
|
|
74
|
-
- `"$ref": "#slot/<id>"`: the same shape as that slot after binding (a grid's selection has the shape of its rows).
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## 5. The bindings file
|
|
79
|
-
|
|
80
|
-
```jsonc
|
|
81
|
-
{
|
|
82
|
-
"version": 3,
|
|
83
|
-
"openapi": "https://apidocs.example.com/index.yaml", // or a path next to this file
|
|
84
|
-
"output": { "dir": "src/api/screens", "client": "@/api/generated" },
|
|
85
|
-
"kinds": [ /* ComponentDescriptor */ ],
|
|
86
|
-
"screens": [ { "id": "collections.worklist.add-notes", "label": "Add Notes",
|
|
87
|
-
"context": [ { "id": "selection", "label": "Selected rows", "many": true,
|
|
88
|
-
"shape": { "from": { "screen": "collections.worklist", "component": "collections.worklist.grid", "slot": "selection" } } } ] } ],
|
|
89
|
-
"components": [ { "id": "collections.worklist.add-notes.notes-type", "kind": "hr-ds/select", "label": "Notes Type" } ],
|
|
90
|
-
"bindings": [ /* SlotBinding */ ]
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
| Slot role | Binding type | Contents |
|
|
95
|
-
|---|---|---|
|
|
96
|
-
| consume | `operation` | operation, request mapping (pointer → expression), response `select`, optional `map`, `load` |
|
|
97
|
-
| consume | `expression` | one expression |
|
|
98
|
-
| trigger | `steps` | ordered `call` steps; a step may run `forEach` over a list; `onError: stop \| continue` |
|
|
99
|
-
| trigger | `none` | reviewed: this event calls no API |
|
|
100
|
-
|
|
101
|
-
Only `confirmed` bindings are generated; drafts are reported as warnings. Bindings describe API calls only: opening, closing, navigation, toasts and reloads are app code.
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## 6. Expressions
|
|
106
|
-
|
|
107
|
-
| Form | Resolves to |
|
|
108
|
-
|---|---|
|
|
109
|
-
| `<componentId>:<slot>[.path]` | A produce slot on the same screen, e.g. `collections.worklist.add-notes.subject:value` |
|
|
110
|
-
| `<expr>[*].<path>` | A property of every item, as a list |
|
|
111
|
-
| `$item[.path]` | The current item: a scoped slot's item, a `forEach` item, or the trigger's payload |
|
|
112
|
-
| `$context.<id>[.path]` | A value declared in the screen's `context` |
|
|
113
|
-
| `$steps.<stepId>.response[.path]` | The response of an earlier step |
|
|
114
|
-
|
|
115
|
-
`:` ends a component id, so ids can contain dots. Nothing outside this grammar is valid.
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## 7. Generated code
|
|
120
|
-
|
|
121
|
-
`api-binding generate` writes into `output.dir`:
|
|
122
|
-
|
|
123
|
-
- `screen-helpers.ts`: the few helpers the hooks share (React Query only).
|
|
124
|
-
- `<screen id>.ts` per screen: `<Screen>State`, `<Screen>Api` and `use<Screen>Api(state)`.
|
|
125
|
-
- `index.ts`: re-exports.
|
|
126
|
-
|
|
127
|
-
**The signature comes from the registry, the body from the bindings.** `<Screen>State` has one optional entry per component that produces values; `<Screen>Api` has one entry per consume and trigger slot. Changing bindings never changes either type, so call sites stay put. Adding or removing components does, and `tsc` points at every call site to update.
|
|
128
|
-
|
|
129
|
-
| Slot | Returns | When unbound |
|
|
130
|
-
|---|---|---|
|
|
131
|
-
| consume | `Data<T>`: `{ bound, data, loading, error }` | `bound: false`; keep the UI's own value |
|
|
132
|
-
| consume, scoped | `Cell<T>`: `{ bound, read(item) }` | `read` returns `undefined` |
|
|
133
|
-
| trigger | `Action`: `{ bound, run(payload?), running, error }` | `run` resolves at once |
|
|
134
|
-
|
|
135
|
-
- Queries call the Orval function for the operation: path params in path order, then the body, then the query params object, then fetch options (headers). A query is enabled only when every required input has a value; `load: { everySeconds }` polls.
|
|
136
|
-
- Actions run steps in order. A failed call rejects `run` (`stop`) or is collected and rejected after the rest run (`continue`).
|
|
137
|
-
- Responses are unwrapped from Orval's `{ data, status, headers }`; a non-2xx status throws `ApiError`.
|
|
138
|
-
|
|
139
|
-
---
|
|
140
|
-
|
|
141
|
-
## 8. Suggestions
|
|
142
|
-
|
|
143
|
-
The studio ranks **all** operations for each slot and shows the best first; any operation can be chosen. Ranking uses metadata only: shape compatibility, intent, name similarity, glossary synonyms. Request fields are proposed from the values the screen has (produce slots, context, earlier steps, the current item), ranked by name and type. Proposals stay `origin: "suggested"` until a person confirms them.
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## 9. Validation
|
|
148
|
-
|
|
149
|
-
`api-binding generate` fails, and writes nothing, on any error:
|
|
150
|
-
|
|
151
|
-
- an id that breaks the format, is duplicated, or clashes with a screen id; a component under no screen
|
|
152
|
-
- an unknown kind, or a parent that is not registered
|
|
153
|
-
- an expression reading a slot that is not produced on the screen, an undeclared context, a later step, or `$item` where there is none
|
|
154
|
-
- an operation not in the docs, a request input the operation does not have, an unconnected path parameter
|
|
155
|
-
- two components whose names become the same TypeScript key
|
|
156
|
-
|
|
157
|
-
Warnings: drafts, unconnected required body fields, and with `--src` every registered id missing from the source (and every `componentId` literal not registered). `--check` exits 1 when generated files are out of date.
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
## 10. Studio rules
|
|
162
|
-
|
|
163
|
-
- Runs
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
1
|
+
# API Binding Framework — v3
|
|
2
|
+
|
|
3
|
+
Normative types: [`contract.ts`](../src/framework/contract.ts).
|
|
4
|
+
|
|
5
|
+
The framework connects **UI components** to **documented API operations** at design time and turns the result into **plain code**. Binding lives in two files: `registry.json` (what exists: kinds and the screen → component → part tree) and `bindings.json` (how it is wired to the API). After design time the app holds only ordinary React Query hooks over an Orval client; no framework code, runtime or manifest ships.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Principles
|
|
10
|
+
|
|
11
|
+
1. **Nothing concrete in the framework.** Component kinds, products, operations and fields arrive as data: kinds and the registry in the bindings file, operations from the OpenAPI docs.
|
|
12
|
+
2. **The API docs are the only source of operations.** No local hooks, hardcoded option lists or free-text overrides.
|
|
13
|
+
3. **UI first.** The unit of work is a screen and its components; APIs attach to component slots.
|
|
14
|
+
4. **Two files.** `registry.json` says what exists (kinds, screens, components); `bindings.json` says how it is wired (output settings, bindings). The studio writes only `bindings.json`.
|
|
15
|
+
5. **No trace at run time.** The generator writes plain hooks; the app never imports this package.
|
|
16
|
+
6. **Deterministic.** The same file and docs always produce the same code. Generated files carry an `@generated` mark and are never edited by hand.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 2. The pieces
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
OpenAPI docs ──► Orval ──────────────► src/api/generated (typed client)
|
|
24
|
+
│
|
|
25
|
+
└──► api-binding studio ◄──► bindings.json (+ registry.json) ──► api-binding generate ──► src/api/screens (one hook per screen)
|
|
26
|
+
│
|
|
27
|
+
app components ── use<Screen>Api(state) ┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
| Piece | What it is | Owned by |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| Kinds | A component kind's slots, with JSON Schema shapes | Design system, in `registry.json` → `kinds` |
|
|
33
|
+
| Screens and components | Every screen and component instance, by id | The app, in `registry.json` (a tree; nesting gives each part its parent) |
|
|
34
|
+
| Bindings | Which operation or value feeds each slot | Designers in the studio, reviewed by engineers |
|
|
35
|
+
| Orval client | Typed functions per operation | Generated by Orval from the same docs |
|
|
36
|
+
| Screen hooks | `use<Screen>Api(state)` per screen | Generated by `api-binding generate` |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 3. Ids
|
|
41
|
+
|
|
42
|
+
One namespace for screens and components, used everywhere: in `componentId` props, in the file and in expressions.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
<product>.<screen>[.<sub-screen>…].<component>[.<part>…]
|
|
46
|
+
collections.worklist screen
|
|
47
|
+
collections.worklist.add-notes sub-screen (a dialog over the worklist)
|
|
48
|
+
collections.worklist.grid component on collections.worklist
|
|
49
|
+
collections.worklist.grid.column.customer-name
|
|
50
|
+
collections.worklist.grid.row.add-notes
|
|
51
|
+
collections.worklist.add-notes.notes-type component on collections.worklist.add-notes
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- Segments are lowercase kebab-case (`[a-z0-9-]`), joined by dots: `^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$`.
|
|
55
|
+
- A component belongs to the **longest** registered screen id that prefixes it. A screen's parent is the longest other screen id that prefixes it. Navigation is built from this; there is no `path` or `parent` to maintain.
|
|
56
|
+
- A component id may not equal a screen id (it would capture that screen's components).
|
|
57
|
+
- Ids are stable: renaming one orphans its bindings. They never depend on labels.
|
|
58
|
+
- In the app, each component gets `componentId="<id>"`; design-system components render it as `data-component-id`.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 4. Kinds and slots
|
|
63
|
+
|
|
64
|
+
A kind declares slots with one of three **roles**:
|
|
65
|
+
|
|
66
|
+
| Role | Meaning | Bound to |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `consume` | The component needs data | An operation (request + response selection) or an expression |
|
|
69
|
+
| `produce` | The component exposes data | Nothing; a source others read |
|
|
70
|
+
| `trigger` | The component emits an event | Ordered API call steps, or "no API" |
|
|
71
|
+
|
|
72
|
+
- `scope.parentSlot`: the slot is evaluated per item of the parent's slot (a cell in a row, an action on a row); `$item` is that item.
|
|
73
|
+
- `feeds`: produce slots of the same component offered as request sources (paging, sorting).
|
|
74
|
+
- `"$ref": "#slot/<id>"`: the same shape as that slot after binding (a grid's selection has the shape of its rows).
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 5. The bindings file
|
|
79
|
+
|
|
80
|
+
```jsonc
|
|
81
|
+
{
|
|
82
|
+
"version": 3,
|
|
83
|
+
"openapi": "https://apidocs.example.com/index.yaml", // or a path next to this file
|
|
84
|
+
"output": { "dir": "src/api/screens", "client": "@/api/generated" },
|
|
85
|
+
"kinds": [ /* ComponentDescriptor */ ],
|
|
86
|
+
"screens": [ { "id": "collections.worklist.add-notes", "label": "Add Notes",
|
|
87
|
+
"context": [ { "id": "selection", "label": "Selected rows", "many": true,
|
|
88
|
+
"shape": { "from": { "screen": "collections.worklist", "component": "collections.worklist.grid", "slot": "selection" } } } ] } ],
|
|
89
|
+
"components": [ { "id": "collections.worklist.add-notes.notes-type", "kind": "hr-ds/select", "label": "Notes Type" } ],
|
|
90
|
+
"bindings": [ /* SlotBinding */ ]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| Slot role | Binding type | Contents |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| consume | `operation` | operation, request mapping (pointer → expression), response `select`, optional `map`, `load` |
|
|
97
|
+
| consume | `expression` | one expression |
|
|
98
|
+
| trigger | `steps` | ordered `call` steps; a step may run `forEach` over a list; `onError: stop \| continue` |
|
|
99
|
+
| trigger | `none` | reviewed: this event calls no API |
|
|
100
|
+
|
|
101
|
+
Only `confirmed` bindings are generated; drafts are reported as warnings. Bindings describe API calls only: opening, closing, navigation, toasts and reloads are app code.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 6. Expressions
|
|
106
|
+
|
|
107
|
+
| Form | Resolves to |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `<componentId>:<slot>[.path]` | A produce slot on the same screen, e.g. `collections.worklist.add-notes.subject:value` |
|
|
110
|
+
| `<expr>[*].<path>` | A property of every item, as a list |
|
|
111
|
+
| `$item[.path]` | The current item: a scoped slot's item, a `forEach` item, or the trigger's payload |
|
|
112
|
+
| `$context.<id>[.path]` | A value declared in the screen's `context` |
|
|
113
|
+
| `$steps.<stepId>.response[.path]` | The response of an earlier step |
|
|
114
|
+
|
|
115
|
+
`:` ends a component id, so ids can contain dots. Nothing outside this grammar is valid.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 7. Generated code
|
|
120
|
+
|
|
121
|
+
`api-binding generate` writes into `output.dir`:
|
|
122
|
+
|
|
123
|
+
- `screen-helpers.ts`: the few helpers the hooks share (React Query only).
|
|
124
|
+
- `<screen id>.ts` per screen: `<Screen>State`, `<Screen>Api` and `use<Screen>Api(state)`.
|
|
125
|
+
- `index.ts`: re-exports.
|
|
126
|
+
|
|
127
|
+
**The signature comes from the registry, the body from the bindings.** `<Screen>State` has one optional entry per component that produces values; `<Screen>Api` has one entry per consume and trigger slot. Changing bindings never changes either type, so call sites stay put. Adding or removing components does, and `tsc` points at every call site to update.
|
|
128
|
+
|
|
129
|
+
| Slot | Returns | When unbound |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| consume | `Data<T>`: `{ bound, data, loading, error }` | `bound: false`; keep the UI's own value |
|
|
132
|
+
| consume, scoped | `Cell<T>`: `{ bound, read(item) }` | `read` returns `undefined` |
|
|
133
|
+
| trigger | `Action`: `{ bound, run(payload?), running, error }` | `run` resolves at once |
|
|
134
|
+
|
|
135
|
+
- Queries call the Orval function for the operation: path params in path order, then the body, then the query params object, then fetch options (headers). A query is enabled only when every required input has a value; `load: { everySeconds }` polls.
|
|
136
|
+
- Actions run steps in order. A failed call rejects `run` (`stop`) or is collected and rejected after the rest run (`continue`).
|
|
137
|
+
- Responses are unwrapped from Orval's `{ data, status, headers }`; a non-2xx status throws `ApiError`.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 8. Suggestions
|
|
142
|
+
|
|
143
|
+
The studio ranks **all** operations for each slot and shows the best first; any operation can be chosen. Ranking uses metadata only: shape compatibility, intent, name similarity, glossary synonyms. Request fields are proposed from the values the screen has (produce slots, context, earlier steps, the current item), ranked by name and type. Proposals stay `origin: "suggested"` until a person confirms them.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 9. Validation
|
|
148
|
+
|
|
149
|
+
`api-binding generate` fails, and writes nothing, on any error:
|
|
150
|
+
|
|
151
|
+
- an id that breaks the format, is duplicated, or clashes with a screen id; a component under no screen
|
|
152
|
+
- an unknown kind, or a parent that is not registered
|
|
153
|
+
- an expression reading a slot that is not produced on the screen, an undeclared context, a later step, or `$item` where there is none
|
|
154
|
+
- an operation not in the docs, a request input the operation does not have, an unconnected path parameter
|
|
155
|
+
- two components whose names become the same TypeScript key
|
|
156
|
+
|
|
157
|
+
Warnings: drafts, unconnected required body fields, and with `--src` every registered id missing from the source (and every `componentId` literal not registered). `--check` exits 1 when generated files are out of date.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 10. Studio rules
|
|
162
|
+
|
|
163
|
+
- Runs from the Vite dev plugin (`api_binding_ui/vite`, serve only) or the CLI (`api-binding studio`). Either server reads `registry.json`, reads and writes `bindings.json`, and fetches the docs server-side, so the browser needs no CORS access.
|
|
164
|
+
- Under the Vite plugin every save regenerates the screen hooks, so a confirmed binding is live in the running app after the save. Vite does not watch `bindings.json`.
|
|
165
|
+
- The dev plugin's floating button opens the studio over the app on the screen shown: the screen most `data-component-id` values the user can see belong to; a component covered by any layer above it does not count. Alt+click opens it on one component. Nothing reaches a build.
|
|
166
|
+
- Navigation and component lists come from the ids. Icons come from kinds.
|
|
167
|
+
- Three slot editors, one per role, generated from slot schemas. A new kind never needs studio code.
|
|
168
|
+
- Shows only what validates.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 11. Conformance rules for an app
|
|
173
|
+
|
|
174
|
+
1. Every bindable component has a `componentId` in the format above, registered in `registry.json`.
|
|
175
|
+
2. Each screen calls its generated `use<Screen>Api(state)` once, passes what its components produce and what it was opened with, and renders `bound ? data : <its own value>`.
|
|
176
|
+
3. No direct API calls in screens other than through generated hooks; no hardcoded data an API should provide.
|
|
177
|
+
4. Opening, closing, navigation, toasts and reloads stay in the app's UI code.
|
|
178
|
+
5. Orval output and screen hooks are regenerated, never edited by hand.
|