Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions content/docs/utilities/runner.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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: <base>`.

#### Serving metadata over HTTP (`?api=<base>`)

`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 <base>/app.json` | the app document (`AppComponentSchema`) |
| `/` | `GET <base>/pages/index.json` | the page document (`PageNodeSchema`) |
| `/customers` | `GET <base>/pages/customers.json` | the page document |
| `/crm/accounts` | `GET <base>/pages/crm/accounts.json` | the page document |

Nested routes map straight through — the request path is `<base>/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: <path>` 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:
Expand Down
48 changes: 31 additions & 17 deletions packages/runner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<base>`, 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 <base>/app.json # the app document, loaded once at startup
GET <base>/pages/index.json # route "/"
GET <base>/pages/customers.json # route "/customers"
GET <base>/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:
Expand Down