Keeping the Hub client in sync
How extract, the bus endpoint, and api:watch fit together so a backend change shows up in the Education Hub.
You added an endpoint, or you exported a bus type. The Hub still has yesterday's types. This page is the loop that closes that gap — both pipes, in the order a developer actually runs them.
Local day-to-day
API repo (Naalya-API):
pnpm bus:extract # chowbea-axios extract → chowbea.bus.json
pnpm bus:watch # extract --watch → re-extract on save
pnpm bus:check # extract --check → CI gate, no write
pnpm prebuild # extract runs automatically before nest buildextract walks *.chowbea.ts (and @chowbea-export tags), validates the closed world, and writes chowbea.bus.json at the repo root. That file is a build artifact. Do not hand-edit it. The repo currently commits it as the served manifest; treat extract as the source of truth anyway.
The server mounts the bus in file mode:
app.use(DEFAULT_API_ROUTE, busHandler());DEFAULT_API_ROUTE is /.well-known/chowbea.json. busHandler() with no argument stats the file per request and re-reads when it changes — so bus:watch plus a running server is live types without a restart. If the manifest is missing, the handler 503s with a hint to run extract. A mid-write file serves last-good.
Swagger stays at /docs/swagger/json. Two URLs, two pipes.
Education Hub:
bun api:watch # poll spec + bus, regenerate _generated
bun api:generate # one-shot from the cached/local spec
bun dev:all # api:watch alongside Viteapi.config.toml points api_endpoint at Swagger and [bus].endpoint at the well-known URL. watch fetches both. If the API is down, REST generate fails with a fetch error; if the bus endpoint 503s, bus output goes stale while REST may still update — run bus:extract on the API.
You almost never run generate by hand. dev:all is the default.
What you may edit
_internal/ caches — never edit, never hand-resolve merge conflicts
_generated/ ALWAYS overwritten
api.types.ts
api.operations.ts
api.contracts.ts
bus/ one file per API barrel + index.ts
api.client.ts generated once, then yours
api.instance.ts generated once — Bearer + refresh live here
api.error.ts
api.helpers.tsRule: _generated/ and _internal/ are projections. Change the OpenAPI spec or the .chowbea.ts barrel. If git reports a conflict under _generated/, run chowbea-axios resolve — do not merge the file like application code.
Checklist after an API change
New/changed REST endpoint
- DTO + controller as usual (Add an Endpoint).
- API running so
/docs/swagger/jsonis current. - Hub
api:watch(orapi:generate) —api.op.yourOperationappears. - Wrap it in a query module; do not call
api.opfrom a component.
New/changed bus type
- Export from a
*.chowbea.tsbarrel — Exporting a type on the bus. pnpm bus:extractin the API repo (or rely onbus:watch).- Confirm
curl -s localhost:8000/.well-known/chowbea.json | headhas your symbol. - Hub
api:watch—_generated/busupdates. - Import from
@/services/api/_generated/bus.
CI
API:
pnpm bus:check
# optional breaking-change tripwire against staging:
pnpm bus:diff # extract --check --diff <staging bus URL> --fail-on-removedHub: the generated-client workflow re-fetches the spec on PRs and fails when _generated is stale. After a bus change, the PR must include the regenerated bus/ files (or the Hub CI equivalent). A Hub PR that uses a new bus symbol without regenerating will not type-check.
Gotchas
readBusManifest() is cwd-relative. Extract from the API repo root. Running the server with a different cwd 503s the bus route.
Global vs project CLI. The API pins chowbea-axios in package.json (^2.7.0 for file-mode busHandler()). A global install delegates to the project's copy when one exists. chowbea-axios status shows which binary is running. The Hub historically used a global CLI for REST — follow that repo's README; the [bus] block still needs a CLI new enough to fetch the manifest.
Unknown chowbeaBus version. If extract writes a newer manifest schema than the Hub CLI understands, fetch fails loud. Upgrade the Hub's CLI; do not hand-edit the version field.
Enums are runtime values on both pipes. Prefer the bus enum (Action from _generated/bus) when you need Action.READ as a value. Do not hand-write a mirror in lib/.