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/README.md
CHANGED
|
@@ -1,80 +1,118 @@
|
|
|
1
|
-
# api_binding_ui
|
|
2
|
-
|
|
3
|
-
Design-time API binding. Register an app's components by id, bind them to documented APIs in a local studio, and generate plain React Query hooks over an [Orval](https://orval.dev) client. Nothing from this package ships in the app.
|
|
4
|
-
|
|
5
|
-
Spec: [`spec/FRAMEWORK.md`](spec/FRAMEWORK.md). Agent rules: [`SKILL.md`](SKILL.md).
|
|
6
|
-
|
|
7
|
-
## Flow
|
|
8
|
-
|
|
9
|
-
1. **Ids.** Every bindable component gets `componentId="<product>.<screen>[.<sub-screen>].<component>"`, e.g. `collections.worklist.add-notes.submit`.
|
|
10
|
-
2. **Register.** List kinds
|
|
11
|
-
3. **
|
|
12
|
-
4. **
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Rebinding changes only the hook bodies; the hook's types come from the registry, so call sites stay put.
|
|
30
|
-
|
|
31
|
-
##
|
|
32
|
-
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
# api_binding_ui
|
|
2
|
+
|
|
3
|
+
Design-time API binding. Register an app's components by id, bind them to documented APIs in a local studio, and generate plain React Query hooks over an [Orval](https://orval.dev) client. Nothing from this package ships in the app.
|
|
4
|
+
|
|
5
|
+
Spec: [`spec/FRAMEWORK.md`](spec/FRAMEWORK.md). Agent rules: [`SKILL.md`](SKILL.md).
|
|
6
|
+
|
|
7
|
+
## Flow
|
|
8
|
+
|
|
9
|
+
1. **Ids.** Every bindable component gets `componentId="<product>.<screen>[.<sub-screen>].<component>"`, e.g. `collections.worklist.add-notes.submit`.
|
|
10
|
+
2. **Register.** List kinds and the screen → component → part tree in `registry.json` (below). The studio's navigation comes from it.
|
|
11
|
+
3. **Docs, once.** Download the OpenAPI file into the repo (e.g. `openapi/collections.yaml`). Orval, the studio and the generator all read that one file.
|
|
12
|
+
4. **Generate, then wire once.** Run Orval (below) and `npx api-binding generate bindings.json`. Every screen gets its hook with nothing bound yet; each screen calls it and falls back to its own values:
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { useCollectionsWorklistAddNotesApi } from "@/api/screens";
|
|
16
|
+
|
|
17
|
+
const api = useCollectionsWorklistAddNotesApi({
|
|
18
|
+
notesType: { selected }, subject: { value: subject }, notes: { value: notes },
|
|
19
|
+
context: { selection: selectedRows },
|
|
20
|
+
});
|
|
21
|
+
const options = api.notesType.options.bound ? api.notesType.options.data ?? [] : sampleOptions;
|
|
22
|
+
|
|
23
|
+
<Button componentId="collections.worklist.add-notes.submit" disabled={api.submit.click.running}
|
|
24
|
+
onClick={async () => { try { await api.submit.click.run(); close(); } catch (e) { toast(String(e)); } }} />
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
5. **Bind.** In a Vite app, add the dev plugin (below), run `vite dev` and click **Bind API**. Each save regenerates the hooks and Vite hot-updates the screen, so a confirmed binding makes real calls right away. Elsewhere, `npx api-binding studio bindings.json` opens it on http://localhost:5180, then run `generate`.
|
|
28
|
+
|
|
29
|
+
Rebinding changes only the hook bodies; the hook's types come from the registry, so call sites stay put.
|
|
30
|
+
|
|
31
|
+
## Dev plugin (Vite)
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// vite.config.ts
|
|
35
|
+
import { bindingStudio } from "api_binding_ui/vite";
|
|
36
|
+
|
|
37
|
+
export default defineConfig({ plugins: [/* …, */ bindingStudio()] }); // reads ./bindings.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- `vite dev` only (`apply: "serve"`); `vite build` output contains nothing from it. Keep `api_binding_ui` a devDependency.
|
|
41
|
+
- Adds a floating **Bind API** button to every HTML page (SPA and server-rendered). It opens the studio over the app on the screen being shown, found from the `data-component-id` attributes the user can see; anything covered by a layer above it does not count.
|
|
42
|
+
- **Alt+click** any component to open the studio on it; holding Alt outlines components.
|
|
43
|
+
- The studio is served by the dev server under `/__binding/`, which reads and writes `bindings.json` and fetches the docs server-side. Writes are same-origin only.
|
|
44
|
+
- Every save, and every edit of `registry.json`, regenerates the screen hooks (errors are printed in the dev server's terminal; nothing is written then). Vite does not watch `bindings.json`, so a save never reloads the page under the studio.
|
|
45
|
+
|
|
46
|
+
## registry.json
|
|
47
|
+
|
|
48
|
+
What exists in the app, as a tree. Every id extends the id it is nested under. People edit it; the studio only reads it.
|
|
49
|
+
|
|
50
|
+
```jsonc
|
|
51
|
+
{
|
|
52
|
+
"version": 1,
|
|
53
|
+
"kinds": [], // slot descriptors per component kind
|
|
54
|
+
"screens": [
|
|
55
|
+
{ "id": "collections.worklist", "label": "Worklist",
|
|
56
|
+
"components": [
|
|
57
|
+
{ "id": "collections.worklist.grid", "kind": "hr-ds/grid", "label": "Grid",
|
|
58
|
+
"children": [ { "id": "collections.worklist.grid.column.customer-name", "kind": "hr-ds/column", "label": "Customer Name" } ] } ],
|
|
59
|
+
"screens": [ // sub-screens that open over it
|
|
60
|
+
{ "id": "collections.worklist.add-notes", "label": "Add Notes", "context": [],
|
|
61
|
+
"components": [ { "id": "collections.worklist.add-notes.submit", "kind": "hr-ds/button", "label": "Submit" } ] } ] }
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## bindings.json
|
|
67
|
+
|
|
68
|
+
```jsonc
|
|
69
|
+
{
|
|
70
|
+
"version": 3,
|
|
71
|
+
"registry": "registry.json",
|
|
72
|
+
"openapi": "openapi/collections.yaml",
|
|
73
|
+
"output": { "dir": "src/api/screens", "client": "@/api/generated" },
|
|
74
|
+
"bindings": [] // written by the studio
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Paths are relative to the file; `output.client` is the import specifier of the Orval output as the app sees it. Without `registry`, `kinds`, `screens` and `components` (flat, with `parent`) can sit in this file instead.
|
|
79
|
+
|
|
80
|
+
## Orval
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// orval.config.ts
|
|
84
|
+
export default {
|
|
85
|
+
api: {
|
|
86
|
+
input: "./openapi/collections.yaml",
|
|
87
|
+
output: {
|
|
88
|
+
target: "src/api/generated/index.ts", // so output.client "@/api/generated" resolves
|
|
89
|
+
client: "fetch",
|
|
90
|
+
mode: "single",
|
|
91
|
+
override: { mutator: { path: "./src/api/fetcher.ts", name: "apiFetch" } }, // auth + base URL; returns { data, status, headers }
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The generated hooks call Orval's functions (named after each `operationId`) and unwrap `{ data, status }`; a non-2xx status throws. A `client: "react-query"` setup with `httpClient: "fetch"` works the same, since it exports the same functions.
|
|
98
|
+
|
|
99
|
+
## CLI
|
|
100
|
+
|
|
101
|
+
| Command | Does |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `api-binding studio [file] [--port 5180]` | Local studio. Reads and writes the file; fetches the docs server-side (no CORS). Accepts requests from localhost only. |
|
|
104
|
+
| `api-binding generate [file]` | Validates, then writes the hooks. Any error: nothing is written. Removes only files it generated. |
|
|
105
|
+
| `api-binding generate [file] --check` | Writes nothing; exits 1 when the hooks are out of date. For CI. |
|
|
106
|
+
| `api-binding generate [file] --src src` | Also lists registered ids missing from the source, and `componentId` literals not registered. |
|
|
107
|
+
|
|
108
|
+
## Develop
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
bun install
|
|
112
|
+
bun run dev # builds the studio and serves it on src/dev/sample/bindings.json
|
|
113
|
+
bun test
|
|
114
|
+
bun run typecheck
|
|
115
|
+
bun run build # library, CLI (dist/cli.js) and studio app (dist/app)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Vite's esbuild binary is blocked on some managed machines, so the build uses Bun.
|
package/SKILL.md
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: api-binding-ui
|
|
3
|
-
description: "Use when adding, changing or wiring screens in an app whose API calls are designed with api_binding_ui: componentId props, bindings.json, generated screen hooks (src/api/screens), the Orval client."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# api_binding_ui: rules for agents
|
|
7
|
-
|
|
8
|
-
Spec: `node_modules/api_binding_ui/spec/FRAMEWORK.md`. The app never imports `api_binding_ui`; it imports the generated hooks.
|
|
9
|
-
|
|
10
|
-
## Never
|
|
11
|
-
|
|
12
|
-
- Import `api_binding_ui` from app code, or add a binding runtime, provider or manifest to the app.
|
|
13
|
-
- Edit files under the Orval output or `src/api/screens`. They start with `// @generated`; regenerate instead.
|
|
14
|
-
- Call APIs from screens other than through the generated screen hook.
|
|
15
|
-
- Add hardcoded option lists, rows or values an API should provide.
|
|
16
|
-
- Put opening, closing, navigation, toasts or reloads into bindings. They are app code.
|
|
17
|
-
- Rename or reuse a `componentId`. Renaming orphans its bindings.
|
|
18
|
-
- Invent APIs. Only operations in the OpenAPI docs exist.
|
|
19
|
-
|
|
20
|
-
## Always
|
|
21
|
-
|
|
22
|
-
1. **componentId on every bindable component**: data, input and action components.
|
|
23
|
-
- Format: `<product>.<screen>[.<sub-screen>…].<component>[.<part>…]`, lowercase kebab-case segments joined by dots. Examples: `collections.worklist.grid`, `collections.worklist.grid.column.customer-name`, `collections.worklist.grid.row.add-notes`, `collections.worklist.add-notes.notes-type`.
|
|
24
|
-
- A dialog or drawer is a sub-screen: its id extends the id of the screen it opens over.
|
|
25
|
-
- A component id never equals a screen id.
|
|
26
|
-
- Components built from config (grid columns, row actions) build the id from the grid's id and the config item's id: `` `${gridId}.column.${kebab(col.id)}` ``.
|
|
27
|
-
2. **Register** every new screen and component in `
|
|
28
|
-
3. **One hook call per screen:**
|
|
29
|
-
```tsx
|
|
30
|
-
const api = use<Screen>Api({ <component>: { <produceSlot>: value }, context: { <id>: value } });
|
|
31
|
-
```
|
|
32
|
-
- Pass every value the screen's components produce: input values, the selected option object (whole object, as `selected`), grid page, sort and selection.
|
|
33
|
-
- Pass what the screen was opened with as `context`.
|
|
34
|
-
4. **Render by `bound`:**
|
|
35
|
-
- `Data`: `api.x.slot.bound ? api.x.slot.data : <the screen's own value>`, with `loading` and `error`.
|
|
36
|
-
- `Cell`: `api.col.value.bound ? api.col.value.read(row) : row.field`.
|
|
37
|
-
- `Action`: `await api.btn.click.run(payload?)`. It rejects on failure. After success, the app decides what happens next (close, toast, reload).
|
|
38
|
-
5. After changing `bindings.json` or the docs: `npx api-binding generate bindings.json --src src`, then typecheck. `tsc` errors show the call sites to update.
|
|
39
|
-
|
|
40
|
-
## New screen checklist
|
|
41
|
-
|
|
42
|
-
- [ ] `componentId` on every component, in the format above
|
|
43
|
-
- [ ] Screen and components registered in `
|
|
44
|
-
- [ ] Hooks regenerated; screen calls `use<Screen>Api(state)` once and passes every produced value
|
|
45
|
-
- [ ] Unbound slots keep the screen's existing content
|
|
1
|
+
---
|
|
2
|
+
name: api-binding-ui
|
|
3
|
+
description: "Use when adding, changing or wiring screens in an app whose API calls are designed with api_binding_ui: componentId props, bindings.json, generated screen hooks (src/api/screens), the Orval client."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# api_binding_ui: rules for agents
|
|
7
|
+
|
|
8
|
+
Spec: `node_modules/api_binding_ui/spec/FRAMEWORK.md`. The app never imports `api_binding_ui`; it imports the generated hooks.
|
|
9
|
+
|
|
10
|
+
## Never
|
|
11
|
+
|
|
12
|
+
- Import `api_binding_ui` from app code, or add a binding runtime, provider or manifest to the app. The only allowed use is `bindingStudio()` from `api_binding_ui/vite` in `vite.config.ts`, with the package as a devDependency.
|
|
13
|
+
- Edit files under the Orval output or `src/api/screens`. They start with `// @generated`; regenerate instead.
|
|
14
|
+
- Call APIs from screens other than through the generated screen hook.
|
|
15
|
+
- Add hardcoded option lists, rows or values an API should provide.
|
|
16
|
+
- Put opening, closing, navigation, toasts or reloads into bindings. They are app code.
|
|
17
|
+
- Rename or reuse a `componentId`. Renaming orphans its bindings.
|
|
18
|
+
- Invent APIs. Only operations in the OpenAPI docs exist.
|
|
19
|
+
|
|
20
|
+
## Always
|
|
21
|
+
|
|
22
|
+
1. **componentId on every bindable component**: data, input and action components.
|
|
23
|
+
- Format: `<product>.<screen>[.<sub-screen>…].<component>[.<part>…]`, lowercase kebab-case segments joined by dots. Examples: `collections.worklist.grid`, `collections.worklist.grid.column.customer-name`, `collections.worklist.grid.row.add-notes`, `collections.worklist.add-notes.notes-type`.
|
|
24
|
+
- A dialog or drawer is a sub-screen: its id extends the id of the screen it opens over.
|
|
25
|
+
- A component id never equals a screen id.
|
|
26
|
+
- Components built from config (grid columns, row actions) build the id from the grid's id and the config item's id: `` `${gridId}.column.${kebab(col.id)}` ``.
|
|
27
|
+
2. **Register** every new screen and component in `registry.json`, nested under the screen or component it belongs to, with its kind and the label users see.
|
|
28
|
+
3. **One hook call per screen:**
|
|
29
|
+
```tsx
|
|
30
|
+
const api = use<Screen>Api({ <component>: { <produceSlot>: value }, context: { <id>: value } });
|
|
31
|
+
```
|
|
32
|
+
- Pass every value the screen's components produce: input values, the selected option object (whole object, as `selected`), grid page, sort and selection.
|
|
33
|
+
- Pass what the screen was opened with as `context`.
|
|
34
|
+
4. **Render by `bound`:**
|
|
35
|
+
- `Data`: `api.x.slot.bound ? api.x.slot.data : <the screen's own value>`, with `loading` and `error`.
|
|
36
|
+
- `Cell`: `api.col.value.bound ? api.col.value.read(row) : row.field`.
|
|
37
|
+
- `Action`: `await api.btn.click.run(payload?)`. It rejects on failure. After success, the app decides what happens next (close, toast, reload).
|
|
38
|
+
5. After changing `bindings.json` or the docs: `npx api-binding generate bindings.json --src src`, then typecheck. `tsc` errors show the call sites to update.
|
|
39
|
+
|
|
40
|
+
## New screen checklist
|
|
41
|
+
|
|
42
|
+
- [ ] `componentId` on every component, in the format above
|
|
43
|
+
- [ ] Screen and components registered in `registry.json`
|
|
44
|
+
- [ ] Hooks regenerated; screen calls `use<Screen>Api(state)` once and passes every produced value
|
|
45
|
+
- [ ] Unbound slots keep the screen's existing content
|