From f3085d9375c559afb94d339f5e1c5d44939294f0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 14:14:17 +0000 Subject: [PATCH] =?UTF-8?q?docs(runner):=20=E8=AE=B0=E5=BD=95=20`api`=20?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=8F=82=E6=95=B0=E8=BF=99=E4=B8=80=E7=9C=9F?= =?UTF-8?q?=E5=AE=9E=E7=9A=84=E5=85=83=E6=95=B0=E6=8D=AE=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=9D=A2=20(#3537)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit runner 有一个真实可用的 API 基址配置面 —— URL 查询参数 `api` —— 但全仓 文档零处记载(#3533 删掉那节不存在的环境变量后,该页关于 API 基址的指引 降为零)。本 PR 只补文档,不动实现。 `content/docs/utilities/runner.mdx` —— `## Configuration` 下新增 `### Metadata Loading`:两种加载策略的分流表、`?api=` 时后端需要提供 的四类请求(`app.json` + `pages{path}.json`)、相对/绝对基址与 CORS、 失败静默转 `null` 的表现、参数只在挂载时读取一次的注意事项,以及缺省 `LocalBundleLoader` 从 `src/app-data/`(gitignore、新检出为空)按序解析的 文件顺序。 `packages/runner/README.md` —— 原 `## Schema Loading` 一节讲的是 runner 并不具备的通用加载方式(`import('./my-schema.json')` / `fetch('/api/schema')`), 与本 PR 要补的正是同一主题;若在其旁另起一节,README 会自相矛盾。因此改写 该节为 `## Metadata Loading`,按 README 既有深度给出实测行为 + 指回文档页。 所有断言均对 origin/main 复核:分流见 `packages/runner/src/App.tsx:43-57`, 基址语义见 `packages/runner/src/lib/MetadataLoader.ts:75-103`。 Fixes #3537 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt --- content/docs/utilities/runner.mdx | 66 +++++++++++++++++++++++++++++++ packages/runner/README.md | 48 ++++++++++++++-------- 2 files changed, 97 insertions(+), 17 deletions(-) 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: