api_binding_ui 2.0.1 → 3.0.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 +56 -50
- package/SKILL.md +29 -34
- package/dist/app/index-0n23yy5r.js +367 -0
- package/dist/app/index-aaxy714b.css +1 -0
- package/dist/app/index.html +7 -0
- package/dist/cli.js +969 -0
- package/dist/codegen/generate.d.ts +22 -0
- package/dist/codegen/helpers.d.ts +2 -0
- package/dist/framework/contract.d.ts +38 -23
- package/dist/framework/expr.d.ts +26 -27
- package/dist/framework/file.d.ts +28 -0
- package/dist/framework/loadCatalog.d.ts +4 -2
- package/dist/framework/store.d.ts +3 -15
- package/dist/{index-3q1vx729.js → index-jj8twdw9.js} +158 -173
- package/dist/index-jj8twdw9.js.map +16 -0
- package/dist/index.d.ts +6 -8
- package/dist/index.js +461 -298
- package/dist/index.js.map +6 -8
- package/dist/studio/BindingStudio.d.ts +6 -11
- package/dist/studio/SettingsPanel.d.ts +5 -7
- package/dist/studio/index.css +38 -149
- package/dist/studio/index.d.ts +1 -2
- package/dist/studio/index.js +318 -386
- package/dist/studio/index.js.map +8 -10
- package/dist/studio/useBindings.d.ts +7 -5
- package/dist/studio/useCatalog.d.ts +9 -2
- package/package.json +65 -53
- package/spec/FRAMEWORK.md +106 -201
- package/dist/framework/runner.d.ts +0 -36
- package/dist/index-3q1vx729.js.map +0 -16
- package/dist/runtime/BindingProvider.d.ts +0 -42
- package/dist/runtime/BindingScreen.d.ts +0 -23
- package/dist/runtime/hooks.d.ts +0 -22
- package/dist/runtime/screenStore.d.ts +0 -7
- package/dist/studio/BindingWidget.d.ts +0 -9
- package/dist/studio/useSettings.d.ts +0 -8
package/package.json
CHANGED
|
@@ -1,53 +1,65 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "api_binding_ui",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "API binding
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"module": "./dist/index.js",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"types": "./dist/index.d.ts",
|
|
12
|
-
"import": "./dist/index.js"
|
|
13
|
-
},
|
|
14
|
-
"./studio": {
|
|
15
|
-
"types": "./dist/studio/index.d.ts",
|
|
16
|
-
"import": "./dist/studio/index.js"
|
|
17
|
-
},
|
|
18
|
-
"./studio.css": "./dist/studio/index.css",
|
|
19
|
-
"./package.json": "./package.json"
|
|
20
|
-
},
|
|
21
|
-
"files": [
|
|
22
|
-
"dist",
|
|
23
|
-
"spec/FRAMEWORK.md",
|
|
24
|
-
"README.md",
|
|
25
|
-
"SKILL.md"
|
|
26
|
-
],
|
|
27
|
-
"sideEffects": [
|
|
28
|
-
"**/*.css"
|
|
29
|
-
],
|
|
30
|
-
"scripts": {
|
|
31
|
-
"dev": "bun
|
|
32
|
-
"build": "rm -rf dist && bun build
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"react
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"@types/
|
|
44
|
-
"@types/react
|
|
45
|
-
"react": "^19.2.0",
|
|
46
|
-
"react
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "api_binding_ui",
|
|
3
|
+
"version": "3.0.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./studio": {
|
|
15
|
+
"types": "./dist/studio/index.d.ts",
|
|
16
|
+
"import": "./dist/studio/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./studio.css": "./dist/studio/index.css",
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"spec/FRAMEWORK.md",
|
|
24
|
+
"README.md",
|
|
25
|
+
"SKILL.md"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": [
|
|
28
|
+
"**/*.css"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"dev": "bun run build:app && node dist/cli.js studio src/dev/sample/bindings.json",
|
|
32
|
+
"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",
|
|
34
|
+
"typecheck": "node ./node_modules/typescript/bin/tsc --noEmit -p tsconfig.json",
|
|
35
|
+
"test": "bun test",
|
|
36
|
+
"prepublishOnly": "bun run typecheck && bun test && bun run build"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"react": ">=18.0.0",
|
|
40
|
+
"react-dom": ">=18.0.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.13.0",
|
|
44
|
+
"@types/react": "^19.2.0",
|
|
45
|
+
"@types/react-dom": "^19.2.0",
|
|
46
|
+
"react": "^19.2.0",
|
|
47
|
+
"react-dom": "^19.2.0",
|
|
48
|
+
"typescript": "^5.8.0",
|
|
49
|
+
"vitest": "^3.0.0"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"yaml": "^2.8.1"
|
|
53
|
+
},
|
|
54
|
+
"bin": {
|
|
55
|
+
"api-binding": "dist/cli.js"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"react": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"react-dom": {
|
|
62
|
+
"optional": true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
package/spec/FRAMEWORK.md
CHANGED
|
@@ -1,271 +1,176 @@
|
|
|
1
|
-
# API Binding Framework —
|
|
1
|
+
# API Binding Framework — v3
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Normative types: [`contract.ts`](../src/framework/contract.ts).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The framework connects **UI components** to **documented API operations** at design time and turns the result into **plain code**. Everything about binding lives in one file, `bindings.json`. After design time the app holds only ordinary React Query hooks over an Orval client; no framework code, runtime or manifest ships.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## 1. Principles
|
|
10
10
|
|
|
11
|
-
1. **Nothing concrete in the framework.**
|
|
12
|
-
2. **The API docs are the only source of operations.**
|
|
13
|
-
3. **UI first.** The unit of work is a screen and its components
|
|
14
|
-
4. **
|
|
15
|
-
5. **
|
|
16
|
-
6. **
|
|
17
|
-
7. **Every contract is versioned.** Descriptors, registry, catalog and manifest each carry a version, and changes are detected, not discovered in production.
|
|
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. **One file.** Kinds, screens, components, bindings and output settings are all in `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.
|
|
18
17
|
|
|
19
18
|
---
|
|
20
19
|
|
|
21
|
-
## 2. The pieces
|
|
22
|
-
|
|
23
|
-
| Piece | What it is | Owned by | Format |
|
|
24
|
-
|---|---|---|---|
|
|
25
|
-
| Component descriptor | A kind's slots, with JSON Schema shapes | Component library (design system) | `ComponentDescriptor` |
|
|
26
|
-
| Screen registry | Every screen in the app, with its component instances | Each product | `ScreenRegistry` (JSON) |
|
|
27
|
-
| Catalog | Normalized operations from the API docs | Generated from OpenAPI with `catalogFromOpenApi` | `Catalog` (JSON) |
|
|
28
|
-
| Binding manifest | Which operation or expression feeds each slot | Designers in the studio, reviewed by engineers | `BindingManifest` (JSON, one per screen) |
|
|
29
|
-
| Extensions | Transforms, context providers, matchers, glossaries, persistence | Framework defaults + products | `FrameworkRegistry` |
|
|
30
|
-
| Studio | Design-time editor that reads all of the above | This package | UI, design mode only |
|
|
31
|
-
| Runtime | Executes manifests in the app | This package | Small library, production |
|
|
20
|
+
## 2. The pieces
|
|
32
21
|
|
|
33
22
|
```
|
|
34
|
-
OpenAPI docs ──►
|
|
35
|
-
|
|
36
|
-
|
|
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) ┘
|
|
37
28
|
```
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
## 3. Component descriptors
|
|
42
|
-
|
|
43
|
-
A descriptor is published once per component kind, next to the component, by whoever builds the component. The framework only understands three slot **roles**:
|
|
44
|
-
|
|
45
|
-
| Role | Meaning | Bound to |
|
|
30
|
+
| Piece | What it is | Owned by |
|
|
46
31
|
|---|---|---|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
32
|
+
| Kinds | A component kind's slots, with JSON Schema shapes | Design system, in `bindings.json` → `kinds` |
|
|
33
|
+
| Screens and components | Every screen and component instance, by id | The app, in `bindings.json` |
|
|
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` |
|
|
50
37
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
**Scoped slots.** A consume slot can be evaluated per item of a parent slot (`scope.parentSlot`). Inside it, `$item` is the parent's current item. This covers cells in rows, items in a list, cards in a lane, without the framework knowing what a row, list or lane is.
|
|
54
|
-
|
|
55
|
-
**Feeding requests.** A consume slot can name produce slots of the same component in `feeds` (for example, paging or sorting state). The studio offers them as sources when mapping that slot's request.
|
|
38
|
+
---
|
|
56
39
|
|
|
57
|
-
|
|
40
|
+
## 3. Ids
|
|
58
41
|
|
|
59
|
-
|
|
42
|
+
One namespace for screens and components, used everywhere: in `componentId` props, in the file and in expressions.
|
|
60
43
|
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
{ "id": "page", "role": "produce", "label": "Page", "schema": { "type": "object", "properties": { "offset": { "type": "integer" }, "limit": { "type": "integer" } } } },
|
|
70
|
-
{ "id": "sort", "role": "produce", "label": "Sort", "schema": { "type": "object", "properties": { "field": { "type": "string" }, "direction": { "enum": ["asc", "desc"] } } } },
|
|
71
|
-
{ "id": "filter", "role": "produce", "label": "Filter", "schema": { "type": "object" } },
|
|
72
|
-
{ "id": "selection", "role": "produce", "many": true, "label": "Selected rows", "schema": { "$ref": "#slot/rows" } },
|
|
73
|
-
{ "id": "rowClick", "role": "trigger", "label": "Row clicked", "schema": { "$ref": "#slot/rows" } }
|
|
74
|
-
] }
|
|
75
|
-
|
|
76
|
-
// A single-choice input
|
|
77
|
-
{ "kind": "example-ds/select", "version": 1, "label": "Dropdown",
|
|
78
|
-
"slots": [
|
|
79
|
-
{ "id": "options", "role": "consume", "many": true, "label": "Options",
|
|
80
|
-
"schema": { "type": "object", "required": ["value", "label"], "properties": { "value": {}, "label": { "type": "string" } } } },
|
|
81
|
-
{ "id": "selected", "role": "produce", "label": "Selected option", "schema": { "$ref": "#slot/options" } },
|
|
82
|
-
{ "id": "change", "role": "trigger", "label": "Changed", "schema": { "$ref": "#slot/options" } }
|
|
83
|
-
] }
|
|
84
|
-
|
|
85
|
-
// A dropdown publishes the whole chosen option, not only its display value: every field the options API
|
|
86
|
-
// returns (an id, a code, a name) is then available as a separate, correctly typed value.
|
|
87
|
-
|
|
88
|
-
// A component that does not exist yet
|
|
89
|
-
{ "kind": "example-ds/timeline", "version": 1, "label": "Timeline",
|
|
90
|
-
"slots": [
|
|
91
|
-
{ "id": "events", "role": "consume", "many": true, "label": "Events",
|
|
92
|
-
"schema": { "type": "object", "required": ["at", "title"], "properties": { "at": { "type": "string", "format": "date-time" }, "title": { "type": "string" }, "detail": { "type": "string" } } } },
|
|
93
|
-
{ "id": "eventClick", "role": "trigger", "label": "Event clicked", "schema": { "$ref": "#slot/events" } }
|
|
94
|
-
] }
|
|
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
|
|
95
52
|
```
|
|
96
53
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
The registry lists the screens that exist in the app, and only those. It is the studio's inventory, so thousands of screens can be bound without opening the app.
|
|
104
|
-
|
|
105
|
-
- Each screen ships its registry entry next to its code (a JSON file or an exported constant). The app's build collects the entries into one registry file.
|
|
106
|
-
- A screen that is not in the app has no entry. Nothing is listed ahead of time.
|
|
107
|
-
- Every component in an entry must have a registered descriptor. An unknown kind fails validation at build time, so the studio never shows a component it cannot bind.
|
|
108
|
-
- `context` declares what a screen receives from outside. Its shape is either a schema or a reference to another screen's produce slot (`shape.from`), so a dialog opened from a grid toolbar automatically knows the selected rows' fields.
|
|
109
|
-
- `parent` groups a dialog or drawer under the screen it appears over, for studio navigation only. How it opens and closes is the app's own UI code.
|
|
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`.
|
|
110
59
|
|
|
111
60
|
---
|
|
112
61
|
|
|
113
|
-
##
|
|
62
|
+
## 4. Kinds and slots
|
|
114
63
|
|
|
115
|
-
|
|
64
|
+
A kind declares slots with one of three **roles**:
|
|
116
65
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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" |
|
|
121
71
|
|
|
122
|
-
|
|
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).
|
|
123
75
|
|
|
124
76
|
---
|
|
125
77
|
|
|
126
|
-
##
|
|
78
|
+
## 5. The bindings file
|
|
127
79
|
|
|
128
|
-
|
|
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
|
+
```
|
|
129
93
|
|
|
130
94
|
| Slot role | Binding type | Contents |
|
|
131
95
|
|---|---|---|
|
|
132
|
-
| consume | `operation` | operation, request mapping (pointer → expression), response `select`, optional `map`
|
|
133
|
-
| consume | `expression` |
|
|
134
|
-
| trigger | `steps` | ordered
|
|
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` |
|
|
135
99
|
| trigger | `none` | reviewed: this event calls no API |
|
|
136
|
-
| produce | — | never bound; produce slots are sources |
|
|
137
|
-
|
|
138
|
-
Bindings describe API calls and nothing else. Opening and closing screens, navigation, reloading after a save and every other UI behaviour belong to the app's code, never to a manifest.
|
|
139
100
|
|
|
140
|
-
|
|
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.
|
|
141
102
|
|
|
142
103
|
---
|
|
143
104
|
|
|
144
|
-
##
|
|
145
|
-
|
|
146
|
-
One grammar for every source. Nothing outside it is allowed.
|
|
105
|
+
## 6. Expressions
|
|
147
106
|
|
|
148
107
|
| Form | Resolves to |
|
|
149
108
|
|---|---|
|
|
150
|
-
| `<
|
|
151
|
-
| `<bindId>.<slot>.<path>` | A property inside it |
|
|
109
|
+
| `<componentId>:<slot>[.path]` | A produce slot on the same screen, e.g. `collections.worklist.add-notes.subject:value` |
|
|
152
110
|
| `<expr>[*].<path>` | A property of every item, as a list |
|
|
153
|
-
| `$item
|
|
154
|
-
| `$context.<id
|
|
155
|
-
|
|
|
156
|
-
| `$steps.<stepId>.response.<path>` | The response of an earlier `call` step |
|
|
157
|
-
| `<expr> \| <transform>(<args>)` | A registered transform |
|
|
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 |
|
|
158
114
|
|
|
159
|
-
|
|
115
|
+
`:` ends a component id, so ids can contain dots. Nothing outside this grammar is valid.
|
|
160
116
|
|
|
161
117
|
---
|
|
162
118
|
|
|
163
|
-
##
|
|
119
|
+
## 7. Generated code
|
|
164
120
|
|
|
165
|
-
|
|
121
|
+
`api-binding generate` writes into `output.dir`:
|
|
166
122
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
| Intent | Slot role and `hints.intents` against the operation's `intent` |
|
|
171
|
-
| Names | Token similarity between screen, component and slot labels and the operation's summary, tags and description |
|
|
172
|
-
| Glossary | Synonym groups from registered glossaries (data files per product) |
|
|
173
|
-
| Fields | For request and `map` entries: name and type similarity between available sources and target fields |
|
|
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.
|
|
174
126
|
|
|
175
|
-
|
|
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.
|
|
176
128
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
- A `required` slot is unbound.
|
|
184
|
-
- A binding refers to a bindId, slot, operation or descriptor version that no longer exists.
|
|
185
|
-
- Schema incompatibility between a source expression and its target (type, `many` vs single, required request fields unmapped).
|
|
186
|
-
- An expression uses a source not available on this screen.
|
|
187
|
-
- A literal is not defined by the target schema.
|
|
188
|
-
- The catalog hash changed: each affected binding is listed with what changed (field removed, renamed or newly required).
|
|
189
|
-
|
|
190
|
-
---
|
|
191
|
-
|
|
192
|
-
## 10. Studio rules
|
|
193
|
-
|
|
194
|
-
The studio renders entirely from descriptors, registry, catalog and manifest:
|
|
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 |
|
|
195
134
|
|
|
196
|
-
-
|
|
197
|
-
-
|
|
198
|
-
-
|
|
199
|
-
- Every consume and trigger slot offers the full catalog in a searchable picker, with matcher suggestions on top.
|
|
200
|
-
- The studio shows only what validates. A component with an unknown kind, or a binding that points at an operation no longer in the catalog, is reported by validation and kept out of the studio until it is fixed.
|
|
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`.
|
|
201
138
|
|
|
202
139
|
---
|
|
203
140
|
|
|
204
|
-
##
|
|
205
|
-
|
|
206
|
-
Entry `api_binding_ui`. Production-safe: it contains no studio code and no styles.
|
|
207
|
-
|
|
208
|
-
```tsx
|
|
209
|
-
<BindingProvider catalog={catalog} manifests={manifests} fetcher={fetcher} transforms={…} providers={…}>
|
|
210
|
-
<BindingScreen id="collections.worklist.add-notes" context={{ selection: selectedRows }}>
|
|
211
|
-
…
|
|
212
|
-
</BindingScreen>
|
|
213
|
-
</BindingProvider>
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
| Hook | Slot role | Returns |
|
|
217
|
-
|---|---|---|
|
|
218
|
-
| `useSlot(bindId, slot)` | consume | `{ status, data, error, reload }`; status is `unbound`, `waiting` (a required input has no value yet), `loading`, `ready` or `error` |
|
|
219
|
-
| `useCell(bindId, slot)` | scoped consume | `(item) => value` for one row or item |
|
|
220
|
-
| `useProduce(bindId, slot, value)` | produce | publishes the component's current value to the screen |
|
|
221
|
-
| `useTrigger(bindId, slot)` | trigger | `{ bound, running, run(item?) }`; `run` performs the API calls and returns each step's outcome |
|
|
141
|
+
## 8. Suggestions
|
|
222
142
|
|
|
223
|
-
|
|
224
|
-
- Requests are built from request pointers and sent through the host's `fetcher`; base URL, authentication and error display stay in the app.
|
|
225
|
-
- A component re-renders only when the values its own binding reads change. Identical requests on one screen share one call.
|
|
226
|
-
- Without a confirmed binding a consume slot is `unbound` and a trigger has `bound: false`. What the component shows or does then is the app's decision.
|
|
227
|
-
- Bindings never open, close, navigate or reload anything. After `run()` resolves, the app decides what happens next (for example close a dialog and call `reload()` on a grid).
|
|
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.
|
|
228
144
|
|
|
229
145
|
---
|
|
230
146
|
|
|
231
|
-
##
|
|
232
|
-
|
|
233
|
-
Entry `api_binding_ui/studio` (plus `api_binding_ui/studio.css`). Import it only from design-time code so production bundles never contain it.
|
|
147
|
+
## 9. Validation
|
|
234
148
|
|
|
235
|
-
-
|
|
236
|
-
- `<BindingStudio>` is the same studio as a plain component, for hosts that mount it on their own page.
|
|
237
|
-
- The studio loads the catalog from the published docs (`docsUrl`, default `https://apidocs.highradius.com/documentation-ui/index.yaml`; JSON or YAML; products from `x-tagGroups`). Each user can change the URL in Settings. A `catalog` passed in `framework` is used instead.
|
|
238
|
-
- Settings also download and import manifests as JSON, so bindings made in the studio can be committed with the app.
|
|
239
|
-
- The widget saves manifests through `BindingProvider persistence` (browser storage by default), so the app runs them immediately. Committed manifests come from Settings → Download.
|
|
149
|
+
`api-binding generate` fails, and writes nothing, on any error:
|
|
240
150
|
|
|
241
|
-
|
|
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
|
|
242
156
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
| Contract | Version field | On change |
|
|
246
|
-
|---|---|---|
|
|
247
|
-
| Descriptor | `version` per kind | Manifests record `kinds`; the studio offers a migration when a kind's version rises |
|
|
248
|
-
| Registry | `registryVersion` | Bumped when the shape changes; a migration ships with the package version that changes it |
|
|
249
|
-
| Catalog | `catalogVersion`, `hash` | bindings to operations no longer in the catalog are reported by validation and hidden in the studio |
|
|
250
|
-
| Manifest | `manifestVersion` | Bumped when the shape changes; a migration ships with the package version that changes it |
|
|
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.
|
|
251
158
|
|
|
252
159
|
---
|
|
253
160
|
|
|
254
|
-
##
|
|
161
|
+
## 10. Studio rules
|
|
255
162
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
5. Dialogs and drawers are separate screens with `parent` set to the screen they appear over.
|
|
261
|
-
6. Manifests are committed to the repository and reviewed like code.
|
|
262
|
-
7. The catalog is regenerated from the docs, never edited by hand.
|
|
163
|
+
- Runs locally from the CLI (`api-binding studio`); it reads and writes `bindings.json` and fetches the docs server-side, so the browser needs no CORS access.
|
|
164
|
+
- Navigation and component lists come from the ids. Icons come from kinds.
|
|
165
|
+
- Three slot editors, one per role, generated from slot schemas. A new kind never needs studio code.
|
|
166
|
+
- Shows only what validates.
|
|
263
167
|
|
|
264
168
|
---
|
|
265
169
|
|
|
266
|
-
##
|
|
170
|
+
## 11. Conformance rules for an app
|
|
267
171
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
172
|
+
1. Every bindable component has a `componentId` in the format above, registered in `bindings.json`.
|
|
173
|
+
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>`.
|
|
174
|
+
3. No direct API calls in screens other than through generated hooks; no hardcoded data an API should provide.
|
|
175
|
+
4. Opening, closing, navigation, toasts and reloads stay in the app's UI code.
|
|
176
|
+
5. Orval output and screen hooks are regenerated, never edited by hand.
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/** Builds requests, reads responses and runs trigger steps. */
|
|
2
|
-
import type { CatalogOperation, OperationConsumeBinding, Step } from "./contract";
|
|
3
|
-
import { type Scope } from "./expr";
|
|
4
|
-
export interface HttpRequest {
|
|
5
|
-
method: string;
|
|
6
|
-
/** Path with parameters filled in, e.g. "/cls/v1/notes/42". */
|
|
7
|
-
path: string;
|
|
8
|
-
query: Record<string, string>;
|
|
9
|
-
headers: Record<string, string>;
|
|
10
|
-
body?: unknown;
|
|
11
|
-
}
|
|
12
|
-
/** Sends a request. Supplied by the host (base URL, auth, errors). */
|
|
13
|
-
export type Fetcher = (request: HttpRequest, operation: CatalogOperation) => Promise<unknown>;
|
|
14
|
-
export declare class BindingError extends Error {
|
|
15
|
-
readonly detail?: unknown | undefined;
|
|
16
|
-
constructor(message: string, detail?: unknown | undefined);
|
|
17
|
-
}
|
|
18
|
-
export declare function buildRequest(op: CatalogOperation, request: Record<string, string>, scope: Scope): HttpRequest;
|
|
19
|
-
/** Identical requests share a key, and so one fetch. */
|
|
20
|
-
export declare const requestKey: (r: HttpRequest) => string;
|
|
21
|
-
/** "$", "$.a.b" or "$.a.items[*]" applied to a response. */
|
|
22
|
-
export declare function select(response: unknown, selector: string): unknown;
|
|
23
|
-
/**
|
|
24
|
-
* Applies a consume binding's `map` to each item. Mapped properties are added to the item, not substituted for it,
|
|
25
|
-
* so a produce slot shaped by this slot (a dropdown's selected option) keeps every field the API returned.
|
|
26
|
-
*/
|
|
27
|
-
export declare function mapItems(data: unknown, binding: OperationConsumeBinding, scope: Scope): unknown;
|
|
28
|
-
export declare function runConsume(binding: OperationConsumeBinding, op: CatalogOperation, scope: Scope, fetcher: Fetcher): Promise<unknown>;
|
|
29
|
-
export interface StepOutcome {
|
|
30
|
-
step: string;
|
|
31
|
-
ok: boolean;
|
|
32
|
-
response?: unknown;
|
|
33
|
-
error?: unknown;
|
|
34
|
-
}
|
|
35
|
-
/** Runs call steps in order. `forEach` runs a call once per item; `onError: "stop"` ends the run at the first failure. */
|
|
36
|
-
export declare function runSteps(steps: Step[], operations: Map<string, CatalogOperation>, scope: Scope, fetcher: Fetcher): Promise<StepOutcome[]>;
|