api_binding_ui 2.0.2 → 3.1.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 +68 -48
- package/SKILL.md +29 -34
- package/dist/app/index-4mgcb4wd.js +367 -0
- package/dist/app/index-aaxy714b.css +1 -0
- package/dist/app/index.html +7 -0
- package/dist/cli.js +1006 -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/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/overlay.js +24 -0
- package/dist/server/handler.d.ts +18 -0
- package/dist/studio/BindingStudio.d.ts +8 -11
- package/dist/studio/SettingsPanel.d.ts +3 -7
- package/dist/studio/index.css +0 -149
- package/dist/studio/index.d.ts +1 -2
- package/dist/studio/index.js +231 -438
- package/dist/studio/index.js.map +8 -10
- package/dist/studio/useBindings.d.ts +7 -5
- package/dist/studio/useCatalog.d.ts +2 -0
- package/dist/vite/plugin.d.ts +36 -0
- package/dist/vite.js +201 -0
- package/package.json +68 -52
- package/spec/FRAMEWORK.md +107 -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/README.md
CHANGED
|
@@ -1,74 +1,94 @@
|
|
|
1
1
|
# api_binding_ui
|
|
2
2
|
|
|
3
|
-
|
|
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
4
|
|
|
5
|
-
|
|
6
|
-
- **Docs only.** APIs come from the OpenAPI documents. If the docs do not have it, it cannot be bound.
|
|
7
|
-
- **API only.** Bindings say which API is called with what data and where results go. Opening, closing and navigation stay in the app.
|
|
5
|
+
Spec: [`spec/FRAMEWORK.md`](spec/FRAMEWORK.md). Agent rules: [`SKILL.md`](SKILL.md).
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
## Flow
|
|
10
8
|
|
|
11
|
-
|
|
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, screens and components in `bindings.json`.
|
|
11
|
+
3. **Client.** Run Orval on the same OpenAPI file (see below).
|
|
12
|
+
4. **Bind.** In a Vite app, add the dev plugin (below) and click **Bind API** on any screen. Elsewhere, `npx api-binding studio bindings.json` opens it on http://localhost:5180. Edits save to the file.
|
|
13
|
+
5. **Generate.** `npx api-binding generate bindings.json` writes one hook per screen into `output.dir`.
|
|
14
|
+
6. **Wire once.** Each screen calls its hook:
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
| `api_binding_ui` | Contract types, `catalogFromOpenApi`, the runtime (`BindingProvider`, `BindingScreen`, `useSlot`, `useCell`, `useProduce`, `useTrigger`), validation | Yes |
|
|
16
|
-
| `api_binding_ui/studio` + `api_binding_ui/studio.css` | `BindingWidget`, `BindingStudio` | No; import only from design-time code |
|
|
16
|
+
```tsx
|
|
17
|
+
import { useCollectionsWorklistAddNotesApi } from "@/api/screens";
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
const api = useCollectionsWorklistAddNotesApi({
|
|
20
|
+
notesType: { selected }, subject: { value: subject }, notes: { value: notes },
|
|
21
|
+
context: { selection: selectedRows },
|
|
22
|
+
});
|
|
23
|
+
const options = api.notesType.options.bound ? api.notesType.options.data ?? [] : sampleOptions;
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<BindingProvider catalog={catalog} manifests={manifests} fetcher={fetcher}>
|
|
24
|
-
<BindingScreen id="collections.worklist.add-notes" context={{ selection }}>
|
|
25
|
-
<AddNotesForm />
|
|
26
|
-
</BindingScreen>
|
|
27
|
-
</BindingProvider>;
|
|
28
|
-
|
|
29
|
-
function AddNotesForm() {
|
|
30
|
-
const notesType = useSlot<{ value: string; label: string }[]>("notesType", "options");
|
|
31
|
-
const [subject, setSubject] = useState("");
|
|
32
|
-
useProduce("subject", "value", subject);
|
|
33
|
-
const submit = useTrigger("submit", "click");
|
|
34
|
-
// render notesType.data, call submit.run(), then close the dialog in your own code
|
|
35
|
-
}
|
|
25
|
+
<Button componentId="collections.worklist.add-notes.submit" disabled={api.submit.click.running}
|
|
26
|
+
onClick={async () => { try { await api.submit.click.run(); close(); } catch (e) { toast(String(e)); } }} />
|
|
36
27
|
```
|
|
37
28
|
|
|
38
|
-
|
|
29
|
+
Rebinding changes only the hook bodies; the hook's types come from the registry, so call sites stay put.
|
|
39
30
|
|
|
40
|
-
##
|
|
31
|
+
## Dev plugin (Vite)
|
|
41
32
|
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
import "api_binding_ui/
|
|
33
|
+
```ts
|
|
34
|
+
// vite.config.ts
|
|
35
|
+
import { bindingStudio } from "api_binding_ui/vite";
|
|
45
36
|
|
|
46
|
-
|
|
47
|
-
<BindingWidget framework={{ descriptors, registry }} />
|
|
37
|
+
export default defineConfig({ plugins: [/* …, */ bindingStudio()] }); // reads ./bindings.json
|
|
48
38
|
```
|
|
49
39
|
|
|
50
|
-
|
|
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 on the page; an open dialog wins over its page.
|
|
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
|
+
|
|
45
|
+
## bindings.json
|
|
46
|
+
|
|
47
|
+
```jsonc
|
|
48
|
+
{
|
|
49
|
+
"version": 3,
|
|
50
|
+
"openapi": "https://apidocs.highradius.com/documentation-ui/index.yaml",
|
|
51
|
+
"output": { "dir": "src/api/screens", "client": "@/api/generated" },
|
|
52
|
+
"kinds": [], // slot descriptors per component kind
|
|
53
|
+
"screens": [], // { id, label, context? }
|
|
54
|
+
"components": [], // { id, kind, label, parent? }
|
|
55
|
+
"bindings": [] // written by the studio
|
|
56
|
+
}
|
|
57
|
+
```
|
|
51
58
|
|
|
52
|
-
|
|
59
|
+
`openapi` is a URL or a path relative to the file; `output.dir` is relative to the file; `output.client` is the import specifier of the Orval output as the app sees it.
|
|
53
60
|
|
|
54
|
-
##
|
|
61
|
+
## Orval
|
|
55
62
|
|
|
63
|
+
```ts
|
|
64
|
+
// orval.config.ts
|
|
65
|
+
export default {
|
|
66
|
+
api: {
|
|
67
|
+
input: "<same OpenAPI URL or file>",
|
|
68
|
+
output: { target: "src/api/generated/index.ts", client: "fetch", mode: "single", baseUrl: "<API base URL>" },
|
|
69
|
+
},
|
|
70
|
+
};
|
|
56
71
|
```
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
|
|
73
|
+
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.
|
|
74
|
+
|
|
75
|
+
## CLI
|
|
76
|
+
|
|
77
|
+
| Command | Does |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `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. |
|
|
80
|
+
| `api-binding generate [file]` | Validates, then writes the hooks. Any error: nothing is written. Removes only files it generated. |
|
|
81
|
+
| `api-binding generate [file] --check` | Writes nothing; exits 1 when the hooks are out of date. For CI. |
|
|
82
|
+
| `api-binding generate [file] --src src` | Also lists registered ids missing from the source, and `componentId` literals not registered. |
|
|
65
83
|
|
|
66
84
|
## Develop
|
|
67
85
|
|
|
68
|
-
```
|
|
86
|
+
```sh
|
|
69
87
|
bun install
|
|
70
|
-
bun run dev # studio
|
|
88
|
+
bun run dev # builds the studio and serves it on src/dev/sample/bindings.json
|
|
71
89
|
bun test
|
|
72
90
|
bun run typecheck
|
|
73
|
-
bun run build
|
|
91
|
+
bun run build # library, CLI (dist/cli.js) and studio app (dist/app)
|
|
74
92
|
```
|
|
93
|
+
|
|
94
|
+
Vite's esbuild binary is blocked on some managed machines, so the build uses Bun.
|
package/SKILL.md
CHANGED
|
@@ -1,50 +1,45 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: api-binding-ui
|
|
3
|
-
description: "Use when adding, changing or wiring screens in an app
|
|
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
4
|
---
|
|
5
5
|
|
|
6
6
|
# api_binding_ui: rules for agents
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Spec: `node_modules/api_binding_ui/spec/FRAMEWORK.md`. The app never imports `api_binding_ui`; it imports the generated hooks.
|
|
9
9
|
|
|
10
10
|
## Never
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
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
19
|
|
|
20
20
|
## Always
|
|
21
21
|
|
|
22
|
-
1. **
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 `bindings.json` (`screens`, `components`), 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 } });
|
|
27
31
|
```
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
- `
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
| Slot role | Hook | Use |
|
|
36
|
-
|---|---|---|
|
|
37
|
-
| consume | `const s = useSlot<T>(bindId, slot)` | render by `s.status`: `unbound` / `waiting` / `loading` / `ready` / `error`; data in `s.data`; `s.reload()` |
|
|
38
|
-
| consume, per row | `const cell = useCell<T>(bindId, slot)` | `cell(row)` |
|
|
39
|
-
| produce | `useProduce(bindId, slot, value)` | publish every value the component exposes; dropdowns publish the whole selected option object as `selected` |
|
|
40
|
-
| trigger | `const t = useTrigger(bindId, slot)` | `if (t.bound) { const out = await t.run(item?); if (out.every(o => o.ok)) { /* app: close, toast, reload */ } else { /* show out.find(o => !o.ok).error */ } }` |
|
|
41
|
-
|
|
42
|
-
4. **Unbound keeps what the screen has today:** `status === "unbound"` renders the screen's existing content (its sample data during design); `ready` replaces it with API data. `t.bound === false` keeps the existing behaviour.
|
|
43
|
-
5. **No per-screen Bind API buttons.** One `<BindingWidget>` near the app root opens the studio on whichever `BindingScreen` is showing.
|
|
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.
|
|
44
39
|
|
|
45
40
|
## New screen checklist
|
|
46
41
|
|
|
47
|
-
- [ ]
|
|
48
|
-
- [ ]
|
|
49
|
-
- [ ]
|
|
50
|
-
- [ ]
|
|
42
|
+
- [ ] `componentId` on every component, in the format above
|
|
43
|
+
- [ ] Screen and components registered in `bindings.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
|