api_binding_ui 3.1.0 → 3.3.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 CHANGED
@@ -1,94 +1,119 @@
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, 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:
15
-
16
- ```tsx
17
- import { useCollectionsWorklistAddNotesApi } from "@/api/screens";
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;
24
-
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)); } }} />
27
- ```
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 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
- ```
58
-
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.
60
-
61
- ## Orval
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
- };
71
- ```
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. |
83
-
84
- ## Develop
85
-
86
- ```sh
87
- bun install
88
- bun run dev # builds the studio and serves it on src/dev/sample/bindings.json
89
- bun test
90
- bun run typecheck
91
- bun run build # library, CLI (dist/cli.js) and studio app (dist/app)
92
- ```
93
-
94
- Vite's esbuild binary is blocked on some managed machines, so the build uses Bun.
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
+ - On start it generates the API client (runs the app's `orval.config.ts`, when present) and the screen hooks, before the server takes requests, so a project can run without committed generated code.
45
+ - 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.
46
+
47
+ ## registry.json
48
+
49
+ 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.
50
+
51
+ ```jsonc
52
+ {
53
+ "version": 1,
54
+ "kinds": [], // slot descriptors per component kind
55
+ "screens": [
56
+ { "id": "collections.worklist", "label": "Worklist",
57
+ "components": [
58
+ { "id": "collections.worklist.grid", "kind": "hr-ds/grid", "label": "Grid",
59
+ "children": [ { "id": "collections.worklist.grid.column.customer-name", "kind": "hr-ds/column", "label": "Customer Name" } ] } ],
60
+ "screens": [ // sub-screens that open over it
61
+ { "id": "collections.worklist.add-notes", "label": "Add Notes", "context": [],
62
+ "components": [ { "id": "collections.worklist.add-notes.submit", "kind": "hr-ds/button", "label": "Submit" } ] } ] }
63
+ ]
64
+ }
65
+ ```
66
+
67
+ ## bindings.json
68
+
69
+ ```jsonc
70
+ {
71
+ "version": 3,
72
+ "registry": "registry.json",
73
+ "openapi": "openapi/collections.yaml",
74
+ "output": { "dir": "src/api/screens", "client": "@/api/generated" },
75
+ "bindings": [] // written by the studio
76
+ }
77
+ ```
78
+
79
+ 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.
80
+
81
+ ## Orval
82
+
83
+ ```ts
84
+ // orval.config.ts
85
+ export default {
86
+ api: {
87
+ input: "./openapi/collections.yaml",
88
+ output: {
89
+ target: "src/api/generated/index.ts", // so output.client "@/api/generated" resolves
90
+ client: "fetch",
91
+ mode: "single",
92
+ override: { mutator: { path: "./src/api/fetcher.ts", name: "apiFetch" } }, // auth + base URL; returns { data, status, headers }
93
+ },
94
+ },
95
+ };
96
+ ```
97
+
98
+ 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.
99
+
100
+ ## CLI
101
+
102
+ | Command | Does |
103
+ |---|---|
104
+ | `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. |
105
+ | `api-binding generate [file]` | Validates, then writes the hooks. Any error: nothing is written. Removes only files it generated. |
106
+ | `api-binding generate [file] --check` | Writes nothing; exits 1 when the hooks are out of date. For CI. |
107
+ | `api-binding generate [file] --src src` | Also lists registered ids missing from the source, and `componentId` literals not registered. |
108
+
109
+ ## Develop
110
+
111
+ ```sh
112
+ bun install
113
+ bun run dev # builds the studio and serves it on src/dev/sample/bindings.json
114
+ bun test
115
+ bun run typecheck
116
+ bun run build # library, CLI (dist/cli.js) and studio app (dist/app)
117
+ ```
118
+
119
+ 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. 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 `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 } });
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 `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
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