diff --git a/content/docs/utilities/runner.mdx b/content/docs/utilities/runner.mdx index f0e8043f12..02b0aabd75 100644 --- a/content/docs/utilities/runner.mdx +++ b/content/docs/utilities/runner.mdx @@ -94,6 +94,72 @@ packages/runner/ ## Configuration +### Metadata Loading + +The Runner picks one of two metadata loaders when it mounts, from the `api` query +parameter of the page URL (`src/App.tsx`). This is the Runner's only API base URL +setting — it reads no environment variables and no config file: + +| URL | Loader | Where metadata comes from | +| --- | --- | --- | +| `http://localhost:5173/` | `LocalBundleLoader` | JSON bundled from `src/app-data/` at build time | +| `http://localhost:5173/?api=/api` | `NetworkLoader` | `fetch`ed from the base URL you passed | + +The branch is `params.get('api')`, so an empty value (`?api=`) is falsy and still +selects the local loader. Which strategy won is logged to the browser console — +`📦 Using Local Bundle Loader` or `🔌 Using Network Loader: `. + +#### Serving metadata over HTTP (`?api=`) + +`NetworkLoader` uses the parameter value verbatim as a base URL and appends fixed +paths to it, so a backend needs to serve exactly two kinds of JSON document: + +| Runner route | Request | Response | +| --- | --- | --- | +| _(any, once at startup)_ | `GET /app.json` | the app document (`AppComponentSchema`) | +| `/` | `GET /pages/index.json` | the page document (`PageNodeSchema`) | +| `/customers` | `GET /pages/customers.json` | the page document | +| `/crm/accounts` | `GET /pages/crm/accounts.json` | the page document | + +Nested routes map straight through — the request path is `/pages` plus the +browser path plus `.json`, with `/` rewritten to `/index` first. + +- **Relative or absolute base.** `?api=/api` keeps the requests same-origin (pair it + with a dev-server proxy or a reverse proxy in front of the built bundle); + `?api=https://metadata.example.com/api` goes cross-origin, which needs CORS on the + backend. The loader calls `fetch` with no second argument, so it sends no + credentials and no custom headers — an endpoint behind cookie or bearer auth will + not work as-is. +- **Failures are silent.** Any non-2xx status, or a network/parse error, is turned + into `null`. The Runner then shows `Page not found: ` for a page, and for a + failed `app.json` it drops the app chrome (header/sidebar) and renders the page on + its own. The HTTP status never reaches the UI — read it from the Network tab. +- **Read once, at mount.** The parameter is captured in a `useMemo` with an empty + dependency list. In-app navigation calls `history.pushState` with the bare path, so + the address bar loses `?api=…` after the first click; the loader already created + keeps serving that session, but reloading or sharing the resulting URL falls back to + the local bundle. +- `NetworkLoader`'s own default base is `/api` (`constructor(baseUrl: string = '/api')`). + That default only applies when the class is constructed directly in code — the + Runner always passes it the query-parameter value. + +#### Running from bundled JSON (no `api` parameter) + +`LocalBundleLoader` resolves metadata from `packages/runner/src/app-data/` through +Vite's `import.meta.glob` — at build time, not over the network. It globs +`app-data/app.json`, `app-data/pages/**/*.json` and `app-data/*.json`, and resolves a +route by trying, in order: + +| Runner route | Files tried, in order | +| --- | --- | +| `/` | `pages/index.json`, `pages/index/index.json`, `index.json` | +| `/customers` | `pages/customers.json`, `pages/customers/index.json` | + +`src/app-data/` is git-ignored (see `packages/runner/.gitignore`) and is **not** part +of a fresh checkout — you supply it, by copying or symlinking your own metadata +directory there. With that directory absent, the three globs compile to `{}`, every +load returns `null`, and `/` renders the built-in `No index page found.` placeholder. + ### `vite.config.ts` The Runner uses Vite for development and building: diff --git a/packages/runner/README.md b/packages/runner/README.md index 16ec6996b6..d9be98b96c 100644 --- a/packages/runner/README.md +++ b/packages/runner/README.md @@ -57,28 +57,42 @@ The runner comes with these plugins pre-configured: - **@object-ui/plugin-charts** - Chart visualization components - **Additional plugins can be added as needed** -## Schema Loading +## Metadata Loading -The runner can load schemas from various sources: +The runner picks its metadata loader when it mounts, from the `api` query parameter of +the page URL (`src/App.tsx`). This is its only API base URL setting — it reads no +environment variables and no config file: -```typescript -// From JSON file -const schema = await import('./my-schema.json'); - -// From JavaScript/TypeScript -const schema = { - type: 'page', - title: 'My App', - body: { - type: 'card', - content: 'Hello World' - } -}; +| URL | Loader | Where metadata comes from | +| --- | --- | --- | +| `http://localhost:5173/` | `LocalBundleLoader` | JSON bundled from `src/app-data/` at build time | +| `http://localhost:5173/?api=/api` | `NetworkLoader` | `fetch`ed from the base URL you passed | + +With `?api=`, the value is used verbatim as a base URL and fixed paths are +appended to it, so a backend only has to serve two kinds of JSON document: -// From API endpoint -const schema = await fetch('/api/schema').then(r => r.json()); +```text +GET /app.json # the app document, loaded once at startup +GET /pages/index.json # route "/" +GET /pages/customers.json # route "/customers" +GET /pages/crm/accounts.json # route "/crm/accounts" ``` +A relative base (`?api=/api`) keeps the requests same-origin; an absolute one needs +CORS on the backend. `fetch` is called with no options, so no credentials or custom +headers are sent, and any non-2xx status or network error becomes `null` — which the +runner renders as a 404 rather than surfacing the status. + +Without the parameter, `LocalBundleLoader` resolves `src/app-data/app.json` and +`src/app-data/pages/**/*.json` through Vite's `import.meta.glob`. That directory is +git-ignored and absent from a fresh checkout, so every load returns `null` until you +copy or symlink your own metadata directory into it. + +Full details — route resolution order, error handling, and the caveat that in-app +navigation drops `?api=` from the address bar — are in the +[Metadata Loading](https://www.objectui.org/docs/utilities/runner#metadata-loading) +section of the docs. + ## Configuration Create a `runner.config.js` file to customize the runner: