1 line
19 KiB
JSON
1 line
19 KiB
JSON
{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"\u2699\ufe0f react-openapi \u2014 Spec-driven Admin UI","text":"<p><code>react-openapi</code> turns an OpenAPI 3.1 document into a complete CRUD admin at runtime: list, detail, create/edit, filtering, foreign-key selects, streaming views and profile screens. Nothing per-resource is hand-written \u2014 the paths and schemas, plus a small set of <code>x-</code> extensions, are the single source of truth.</p> <p>Doc model: this wiki is written for humans \u2014 architecture, the extension catalogue, and UI recipes. The authoritative API contracts live in the code (RDFC JSDoc) and the machine-readable bundle under <code>docs/mcp/</code>. Browse the TypeDoc site for the full symbol reference.</p>"},{"location":"#key-features","title":"\ud83d\ude80 Key Features","text":"<ul> <li>\ud83d\udcc4 Runtime spec loading \u2014 one built UI works against any backend that serves a spec at a known URL</li> <li>\ud83e\uddf1 Derives everything \u2014 <code>ResourceConfig</code> per collection, routes, filter widgets, FK options and form/validation wiring</li> <li>\ud83c\udf9b\ufe0f <code>x-</code> extensions as the contract \u2014 display formats, list columns, hidden fields, ordering, FKs, uploads, SSE, profile and auth endpoints</li> <li>\ud83e\udde9 Renderer dispatch \u2014 each <code>FieldConfig</code> resolves to the right MUI control (select, switch, date picker, upload, chips, FK autocomplete)</li> <li>\ud83d\udce1 Server-Sent Events \u2014 <code>x-sse</code> resources render as live streams with reactive connection status</li> <li>\ud83d\udd10 Auth wiring \u2014 Bearer tokens from a <code>getToken</code> callback, plus auth endpoints derived from <code>securitySchemes</code></li> </ul>"},{"location":"#documentation-structure","title":"\ud83d\udce6 Documentation Structure","text":"Section Description Overview Architecture, data flow and quick start Spec Extensions Every <code>x-</code> extension, its level and its validator rule Admin UI Components, hooks, field renderers, SSE and auth"},{"location":"#quick-start","title":"\ud83d\ude80 Quick Start","text":"<pre><code>import { AppProvider, Admin } from \"react-openapi\";\n\n<AppProvider specConfiguration={{ specUrl: \"/openapi.json\" }}>\n <Admin basePath=\"/admin\" />\n</AppProvider>\n</code></pre> <p>Requires React 18, React Router and MUI (peer dependencies). <code>Admin</code> and <code>ProfileRoutes</code> must be nested inside an <code>AppProvider</code> and a router.</p>"},{"location":"#related-resources","title":"\ud83d\udd17 Related Resources","text":"<ul> <li>Source Code: the <code>react-openapi</code> repository (<code>README.md</code> carries the full spec-authoring guide)</li> <li>TypeDoc site: the generated <code>lib</code> HTML for the full API reference</li> <li>MCP Bundle: <code>docs/mcp/</code> (<code>docs://modules/react-openapi</code>)</li> <li>Library Registry: registered in the standards hub as <code>react-openapi</code> (MCP <code>:8012</code>)</li> <li>Auth Server: the contract behind <code>securitySchemes.bearerAuth</code> \u2014 read <code>docs://spec</code> on the <code>standards</code> MCP</li> <li>vet: the demo app built on this package (MCP <code>:8013</code>)</li> </ul> <p>\u00a9 Aetoskia Internal \u2014 <code>react-openapi</code> 0.0.1</p>"},{"location":"01_overview/","title":"Library Overview","text":"<p><code>react-openapi</code> is a compiler that runs in the browser: it reads an OpenAPI document, validates it, transforms it into an internal resource model, and renders UI from that model. It never edits your spec, and it never falls back silently when a required extension is missing.</p>"},{"location":"01_overview/#architecture","title":"\ud83c\udfd7\ufe0f Architecture","text":"<pre><code>OpenAPI document (runtime fetch)\n |\n spec-loader -> parsed spec object\n |\n spec-validator -> errors / warnings / info messages\n |\n transformers -> ResourceConfig[] + FieldConfig[] + relationships\n |\n AppProvider -> AppContext (resources, schemas, auth, api client)\n |\n Admin / renderers -> routes, tables, forms, streams\n</code></pre> Layer Module Responsibility Load <code>spec-loader.ts</code> fetch JSON/YAML, resolve <code>$ref</code> Validate <code>spec-validator.ts</code> <code>error</code> / <code>warning</code> / <code>info</code> rules Transform <code>transformers/*</code> schema \u2192 <code>ResourceConfig</code>, properties \u2192 <code>FieldConfig</code> State <code>context/AppProvider.tsx</code>, <code>AppContext</code> shared resources, schemas, auth, axios client HTTP <code>hooks/useApi.ts</code> <code>initApi</code>, <code>getApi</code> with bearer injection Present <code>components/*</code> <code>Admin</code>, <code>ResourceList/Detail/Form</code>, renderers"},{"location":"01_overview/#quick-start","title":"\ud83d\udce6 Quick start","text":"<pre><code>import { AppProvider, Admin } from \"react-openapi\";\nimport type { SpecConfiguration } from \"react-openapi\";\n\nconst specConfiguration: SpecConfiguration = {\n specUrl: \"/api/openapi.yaml\",\n baseApiUrl: \"https://api.example.com/v1\",\n title: \"Vet Clinic Admin\",\n getToken: () => localStorage.getItem(\"token\"),\n};\n\nexport default function App() {\n return (\n <AppProvider specConfiguration={specConfiguration}>\n <Admin basePath=\"/admin\" />\n </AppProvider>\n );\n}\n</code></pre> <p><code>Admin</code> renders <code>SideMenu</code> plus nested routes per resource: <code>/<basePath>/<resource></code> (list), <code>/<resource>/new</code>, <code>/<resource>/:id</code> (detail), <code>/<resource>/:id/edit</code>. Streaming (<code>x-sse</code>) resources render only the list.</p>"},{"location":"01_overview/#how-one-resource-reaches-the-screen","title":"\ud83d\udd04 How one resource reaches the screen","text":"<ol> <li>The spec is fetched at mount; <code>specValidator</code> runs over it.</li> <li>Every schema carrying <code>x-resource</code> becomes a <code>ResourceConfig</code> (<code>name</code>, <code>schemaName</code>, <code>displayName</code>, <code>path</code>, <code>streaming</code>, pagination).</li> <li>Each property becomes a <code>FieldConfig</code> \u2014 <code>x-order</code>, <code>x-label</code>, <code>x-hidden</code>, <code>x-filterable</code>, <code>x-sortable</code>, <code>x-edit-as</code>, <code>x-ui-type</code>.</li> <li><code>useResource(name)</code> adds CRUD helpers (<code>list</code>, <code>get</code>, <code>create</code>, <code>update</code>, <code>remove</code>, <code>stream?</code>) for one resource.</li> <li><code>ListCellRenderer</code> / <code>DetailFieldRenderer</code> / <code>FormFieldRenderer</code> dispatch to the concrete control for that <code>FieldConfig</code>.</li> </ol>"},{"location":"01_overview/#design-decisions","title":"\u26a0\ufe0f Design decisions","text":"<ul> <li>Spec loaded at runtime, not compiled into the bundle.</li> <li>Schema-to-path mapping is explicit \u2014 <code>x-resource</code> must equal the path segment. No pluralisation guessing.</li> <li>No fallbacks for required extensions \u2014 missing <code>x-primary-key</code>, <code>x-display-format</code>, <code>x-list-columns</code>, <code>x-label</code> or <code>x-order</code> fails at transform time rather than defaulting silently.</li> <li>Strict response parsing \u2014 paginated lists are <code>{ total?, items[] }</code>, non-paginated lists are a plain array. No fallback chains.</li> <li>FK options use <code>?limit=0</code> on paginated targets: a server convention meaning \"return all\", not documented in the spec.</li> </ul>"},{"location":"01_overview/#read-next","title":"\u27a1\ufe0f Read Next","text":"<ul> <li>02 \u2013 Spec Extensions</li> </ul>"},{"location":"02_spec_extensions/","title":"Spec Extensions","text":"<p><code>react-openapi</code> is configured entirely by <code>x-</code> extensions inside the OpenAPI document. This page is the catalogue; the repository <code>README.md</code> carries the complete authoring guide with a minimal worked spec.</p> <p>Validation levels \u2014 <code>error</code> (the admin will not render), <code>warning</code> (renders, shown in a snackbar), <code>info</code> (informational note).</p>"},{"location":"02_spec_extensions/#schema-level","title":"\ud83d\udce6 Schema level","text":"Extension Required Purpose <code>x-resource</code> \u2705 Marks the schema as a resource; value must equal the collection path segment (<code>pets</code> \u2192 <code>/pets</code>). Schemas without it are skipped entirely (use for <code>ErrorBody</code>, <code>Metadata</code>, \u2026). <code>x-primary-key</code> \u2705 Property used as the <code>:id</code> path parameter. Must exist in <code>properties</code>. <code>x-display-format</code> \u2705 Template such as <code>\"{name} - #{id}\"</code>; used for row labels, detail titles and FK options. <code>x-list-columns</code> \u2705 Ordered property names for the datatable; every entry must exist."},{"location":"02_spec_extensions/#property-level","title":"\ud83d\udce6 Property level","text":"Extension Required Purpose <code>x-label</code> \u2705 Label for form field, table header and detail view. <code>x-order</code> \u2705 Field ordering; ties break alphabetically. <code>x-description</code> \u2014 Helper text / placeholder; falls back to <code>x-label</code>. <code>x-hidden</code> \u2014 <code>{ form, list, detail }</code> visibility; missing keys default to <code>false</code>. <code>x-filterable</code> \u2014 Marks the field filterable (UI integration reserved). <code>x-sortable</code> \u2014 Sends <code>?sort=field</code> / <code>?sort=-field</code> on the list endpoint. <code>x-edit-as</code> \u2014 e.g. <code>date</code> \u2014 edit a <code>date-time</code> field with a date input. <code>x-fk</code> \u2014 <code>{ resource, prefetch }</code> \u2014 render a dropdown or multi-select for a <code>$ref</code>. <code>x-ui-type</code> \u2014 <code>image</code> \u2014 file/image upload control. <code>x-upload</code> / <code>x-upload-url</code> \u2014 Raw-binary upload (<code>type: image\\|pdf\\|html\\|csv\\|file</code>) or an image POST endpoint with <code>{id}</code>."},{"location":"02_spec_extensions/#foreign-keys","title":"Foreign keys","text":"<pre><code>owner:\n $ref: '#/components/schemas/Parent'\n x-fk:\n resource: parents # the target's x-resource value, not the schema name\n prefetch: true # false -> load options on focus\n</code></pre> <ul> <li>Single <code>$ref</code> \u2192 <code>FkSelectField</code> dropdown; <code>type: array</code> \u2192 <code>FkMultiSelectField</code>.</li> <li>Options are fetched as <code>GET /{target}?limit=0</code> (paginated) or <code>GET /{target}</code>, then formatted with the target's <code>x-display-format</code>.</li> <li>Validator rules: <code>resource</code> must match some <code>x-resource</code>, and that target must have <code>x-display-format</code> + <code>x-primary-key</code>.</li> <li>A <code>$ref</code> without <code>x-fk</code> renders read-only (<code>InlineRefField</code> chips, or a disabled JSON text field in forms) and emits an <code>info</code> message.</li> </ul>"},{"location":"02_spec_extensions/#path-level","title":"\ud83d\udce6 Path level","text":"Extension Purpose <code>x-sse</code> Marks a path as a Server-Sent Events stream instead of REST CRUD. <pre><code>/pets/events:\n x-sse: true\n get:\n summary: Subscribe to Pet events\n responses:\n 200:\n description: SSE stream of Pet change events\n content:\n text/event-stream:\n schema: { type: object }\n</code></pre> <p>An <code>x-sse</code> resource is excluded from the sidebar, list/detail routes and CRUD hooks \u2014 it appears only in the streaming collection and is reachable through <code>useResource(name).stream</code> / <code>SseStreamView</code>.</p>"},{"location":"02_spec_extensions/#security-auth","title":"\ud83d\udce6 Security / auth","text":"<p>Derived from <code>components.securitySchemes.bearerAuth</code>:</p> Extension Meaning <code>x-server-url</code> Auth server base URL \u2192 <code>AuthConfig.serverUrl</code> <code>x-login-path</code> \u2192 <code>loginPath</code> <code>x-register-path</code> \u2192 <code>registerPath</code> <code>x-logout-path</code> \u2192 <code>logoutPath</code> <code>x-me-path</code> \u2192 <code>mePath</code> <code>x-introspect-path</code> \u2192 <code>introspectPath</code> <p>Requests carry <code>Authorization: Bearer <token></code> from <code>SpecConfiguration.getToken</code>. On a <code>401</code> the stored token is cleared so a refresh flow can take over.</p>"},{"location":"02_spec_extensions/#profile-x-profile","title":"\ud83d\udce6 Profile (<code>x-profile</code>)","text":"<p><code>x-profile</code> operations describe the current-user view/edit/create screens. <code>SpecConfiguration.profileComponents</code> supplies React components for them, and they are mounted through the exported <code>ProfileRoutes</code>.</p>"},{"location":"02_spec_extensions/#validator-rules-summary","title":"\u2705 Validator rules (summary)","text":"<p>For each schema with <code>x-resource</code>:</p> <ol> <li><code>x-primary-key</code>, <code>x-display-format</code>, <code>x-list-columns</code> present and valid.</li> <li>Every property has <code>x-label</code> and <code>x-order</code>.</li> <li><code>$ref</code> without <code>x-fk</code> \u2192 <code>info</code>; <code>x-fk</code> target must exist and be displayable.</li> <li>The <code>x-resource</code> path exists with GET + POST.</li> <li>The item path <code>/{resource}/{id}</code> exists with GET + PUT + DELETE.</li> <li>If pagination params exist, <code>limit</code> must carry a <code>schema.default</code>.</li> </ol>"},{"location":"02_spec_extensions/#related","title":"Related","text":"<ul> <li>01 \u2013 Overview \u00b7 03 \u2013 Admin UI</li> </ul>"},{"location":"03_admin_ui/","title":"Admin UI","text":"<p>Everything on screen comes from the loaded spec. This page lists the pieces you compose with, and the three ways to override the generated behaviour.</p>"},{"location":"03_admin_ui/#components","title":"\ud83d\udce6 Components","text":"Export Purpose <code>AppProvider</code> Loads and validates the spec, builds resource configs, initialises the axios client, provides context. Wrap the app with it. <code>Admin</code> Sidebar + nested routes for every resource. Takes <code>basePath</code>. <code>ProfileRoutes</code> Routes for <code>x-profile</code> view/edit/create operations. <code>ListCellRenderer</code> One list/table cell; honours FK display formats, inline refs, enums, booleans. <code>DetailFieldRenderer</code> Read-only label + value pair in the detail view. <code>FormFieldRenderer</code> Editable form field; dispatches to the concrete control. <code>FilterBar</code> / <code>SideMenu</code> / <code>Layout</code> Layout primitives used by <code>Admin</code>. <code>ValidationAlert</code> Spec validation errors/warnings surface. <code>SseStreamView</code> Live stream card: status, event counter, latest event, snackbar. <code>SseConnectionStatus</code> Green/red dot with Connected/Disconnected \u2014 reactive everywhere. <code>MultiEnumField</code>, <code>CurrencyField</code>, <code>FkMultiSelectField</code> Exported controls you can reuse in custom screens."},{"location":"03_admin_ui/#field-renderer-dispatch","title":"Field renderer dispatch","text":"Condition Form control List / detail <code>x-fk</code>, not an array <code>FkSelectField</code> display-format cell <code>x-fk</code>, <code>type: array</code> <code>FkMultiSelectField</code> chips <code>enum</code> <code>EnumField</code> chip <code>type: boolean</code> <code>BooleanField</code> (switch) Yes/No chip <code>type: integer</code> / <code>number</code> <code>NumberField</code> typography <code>format: date</code> / <code>date-time</code> <code>DateField</code> typography <code>x-ui-type: image</code> / <code>x-upload</code> upload control avatar / chip <code>$ref</code> without <code>x-fk</code> disabled JSON field <code>InlineRefField</code> chips otherwise <code>StringField</code> typography"},{"location":"03_admin_ui/#hooks","title":"\ud83d\udce6 Hooks","text":"<pre><code>const { config, resources, schemas, loading, errors, warnings } = useAppContext();\n\nconst { resource, components, list, get, create, update, remove, stream, loading, error } =\n useResource(\"pets\");\n</code></pre> <ul> <li><code>useAppContext()</code> \u2014 the whole derived model.</li> <li><code>useResource(name)</code> \u2014 CRUD + optional <code>stream(handlers, pathParams?) \u2192 { close }</code> for <code>x-sse</code> resources; <code>components</code> holds the per-resource filter widgets.</li> <li><code>useItemSse(url, handlers)</code> \u2014 subscribe to one item's event stream.</li> <li><code>useFkFieldOptions(...)</code> / <code>getApi()</code> \u2014 FK options and the configured axios instance for custom calls.</li> <li><code>sanitizePayload(payload, resolver)</code> \u2014 strips/normalises fields before create/update.</li> </ul>"},{"location":"03_admin_ui/#three-ways-to-customise","title":"\ud83d\udce6 Three ways to customise","text":""},{"location":"03_admin_ui/#1-per-resource-behaviour-specconfigurationresourceconfig","title":"1. Per-resource behaviour \u2014 <code>specConfiguration.resourceConfig</code>","text":"<pre><code><AppProvider specConfiguration={{\n specUrl: \"/api/openapi.yaml\",\n resourceConfig: {\n invoices: { filterOptions: { mode: \"sidebar\" }, fieldTypes: { amount: \"currency\" } },\n },\n}} />\n</code></pre>"},{"location":"03_admin_ui/#2-profile-screens-specconfigurationprofilecomponents","title":"2. Profile screens \u2014 <code>specConfiguration.profileComponents</code>","text":"<pre><code><AppProvider specConfiguration={{\n specUrl: \"/api/openapi.yaml\",\n profileComponents: { view: ProfileView, edit: ProfileEdit, create: ProfileCreate },\n}} />\n</code></pre>"},{"location":"03_admin_ui/#3-your-own-screens-hooks","title":"3. Your own screens \u2014 hooks","text":"<pre><code>import { useAppContext, SseStreamView } from \"react-openapi\";\n\nfunction MyStreamPage() {\n const { resources } = useAppContext();\n const res = resources.find((r) => r.name === \"calls\");\n if (!res) return null;\n return <SseStreamView resource={res} />;\n}\n</code></pre>"},{"location":"03_admin_ui/#streaming","title":"\ud83d\udce1 Streaming","text":"<p><code>useResource(\"calls\").stream({ onEvent, onError, onOpen })</code> returns a subscription with <code>close()</code>. Reconnection is browser-native; components read connection state from <code>SseConnectionStatus</code>.</p>"},{"location":"03_admin_ui/#auth","title":"\ud83d\udd10 Auth","text":"<p><code>getToken</code> is called per request and injected as <code>Authorization: Bearer</code>. Auth endpoints are derived from the spec's <code>securitySchemes.bearerAuth</code> extensions (see Spec Extensions).</p> <p>Combine with react-auth</p> <p><code>react-auth</code> owns the login/register UI and the session; <code>react-openapi</code> only needs a token. Feed <code>getToken: tokenStore.get</code> into <code>specConfiguration</code> to share one session between the two.</p>"},{"location":"03_admin_ui/#related","title":"Related","text":"<ul> <li>01 \u2013 Overview \u00b7 02 \u2013 Spec Extensions</li> </ul>"}]} |