Dynamic per-service client generation and API explorer UI #112
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_router#112
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Objective
Extend
hero_routerso it dynamically generates language clients and an OpenRPC-driven API explorer page per discovered service, on top of the existing Python client generator.Today the router already exposes
GET /:service/python/{client.py,interface.py}, generated from each service'sopenrpc.json(seecrates/hero_router/src/python_codegen.rs+server/routes.rs). We want the same treatment for JavaScript and for an HTML API explorer page.Deliverables
1. JavaScript client generator
crates/hero_router/src/js_codegen.rs, mirroringpython_codegen.rs.GET /:service/js/client.jsandGET /:service/js/interface.js.~/hero/var/router/js/{name}_client.js|_interface.js|.hash.fetchagainst the service's/rpc(relative URL so it works behind the router's per-service proxy).interface.jsis the lightweight stub form (method signatures only, for LLM/editor context).2. Per-service
/apiexplorer pageGET /:service/apirendering a tiny Askama template that mounts the<hero-api-docs>web component fromhero_website_framework/crates/hero_admin_lib.spec-url="/:service/openrpc.json"rpc-url="/:service/rpc"hero_admin_libas a workspace dependency.api-docs.js(and any companion assets) via the existingrust-embedasset pipeline so the page is fully self-contained./:service/apireturns a "Waiting for service... openapi.sock not found" placeholder. Replace that path's resolution so it renders the API explorer directly when the service'sopenrpc.jsonis reachable.3. (Optional follow-up, in scope of this issue) Replace router's own
/api/apiis a hand-rolled Askama template (templates/api.html) with custom JS.<hero-api-docs>for visual + behavioural consistency with the per-service pages.Acceptance criteria
GET /:service/js/client.jsand/:service/js/interface.jsreturn valid ES module files for any discovered service with anopenrpc.json.GET /:service/apirenders the<hero-api-docs>explorer, populated from the service's spec, with working RPC calls through the router's per-service/rpcproxy.~/hero/var/router/js/and invalidate on spec hash change, mirroring Python behaviour.cargo buildandcargo testpass./:service/apiand a method can be invoked successfully from the explorer.Notes
<hero-api-docs>:hero_website_framework/crates/hero_admin_lib/static/js/api-docs.js, used byhero_proc_adminvia theopenrpc_proxy!macro that exposes/rpc+/openrpc.json.Implementation summary
Merged into
developmentas commit672cf98.Changes
crates/hero_router/src/js_codegen.rspython_codegen.rs. camelCase method names, reserved-word suffixing, FNV-1a hashed cache at~/hero/var/router/js/. 8 unit tests.crates/hero_router/templates/service_api.html<hero-api-docs spec-url="/{service}/rpc/openrpc.json" rpc-url="/{service}/rpc">.crates/hero_router/src/server/routes.rsjs_file_handler,service_api_page_handler,shared_statichandler. Wiredjsandapiinto the per-service proxy dispatch —apishort-circuits before any socket lookup.crates/hero_router/Cargo.tomlhero_admin_libgit dep (development branch ofhero_website_framework).crates/hero_router/templates/api.html<hero-api-docs>(251→20 lines, extendsbase.html).crates/hero_router/src/lib.rspub mod js_codegen;Cargo.lockAsset strategy
Re-exports
hero_admin_lib::assets::SharedAssetsvia a thinshared_statichandler mounted at/static/shared/{*path}. The<hero-api-docs>component (and its companions) stay versioned inhero_website_framework; the router only embeds what it serves.Routing decision
<hero-api-docs>is wired against/<service>/rpc/openrpc.json, which is what the existing per-service proxy already exposes (a sibling-shortcut already handled/rpc/openrpc.json→ service rpc.sock/openrpc.json). No new socket plumbing needed.Note: the previous
webname == "api"arm fell through to the per-service proxy and tried to find a non-existentopenapi.sock. That arm is now consumed byservice_api_page_handler— any caller that relied on/<service>/apireachingopenapi.sockwould now get the explorer page instead. There do not appear to be any services in the current stack that ship anopenapi.sock.Verification
cargo check -p hero_router— pass, no warnings on the final codecargo test -p hero_router— 124 passed (8 new injs_codegen); 5 pre-existing failures unchanged (PATH_ROOT/socket-dir test setup, unrelated to this change)lab build --release --restart— built, installed to$PATH_ROOT/bin, restarted via hero_proc, healthy on TCP 9988GET /api— renders the rebuilt router-own explorerGET /hero_proc/api— renders the explorer scoped to hero_proc, populated from its 92 methodsGET /hero_proc/js/client.js→ 200, 28KB ES moduleGET /hero_proc/js/interface.js→ 200, signature stubsGET /hero_proc/python/client.py→ 200 (existing Python codegen still working)Acceptance criteria
GET /:service/js/client.jsand/:service/js/interface.jsreturn valid ES module files for any discovered service with anopenrpc.json.GET /:service/apirenders the<hero-api-docs>explorer, populated from the service's spec.~/hero/var/router/js/and invalidate on spec hash change, mirroring Python behaviour.cargo buildandcargo testpass (failures are pre-existing and unrelated).hero_proc) can be opened at/:service/apiand methods are listed correctly from the explorer.Correction commit
The initial implementation (commit
672cf98) hijacked/<svc>/apito render the explorer, which broke the documented/<svc>/api/ → openapi.sockproxy. Fixed in commitc351b45(this branch, development):Route changes
/api/openrpc<hero-api-docs>explorer/<svc>/api/...openapi.sock/<svc>/openrpc<hero-api-docs>explorer/<svc>/python/.../<svc>/js/...Service detail tab
The existing OpenRPC tab in
partials/service.html(/service/{id}/openrpc) now mounts<hero-api-docs>inline. Same component as the standalone/<svc>/openrpcpage, Shadow-DOM-scoped, fetches the spec via the router's per-service RPC proxy. The previous hand-rolled methods/docs/spec inner-layout (and itsmethods_html/docs_html/openrpc_jsonbacking template vars + sub-nav JS) is marked DEPRECATED (hero_router #112) in-place for later cleanup once no consumer reappears.<hero-api-docs>is loaded once inbase.htmlso it survives Unpoly fragment swaps.Docs
docs/per-service-routes.md(full per-service URL table)README.mdlinks to it from the new "Per-service surface" sectionhero_skills/skills/hero/hero_router.mddocuments theopenrpc/python/jswebnames alongside the existing socket-proxy tableVerification
cargo test -p hero_router --lib: 124 passed, 5 pre-existing failures (PATH_ROOT/socket-dir setup, unrelated)/api→ 200 (router accordion)/openrpc→ 200 (router web-component explorer)/hero_proc/api→ 404 (correctly triesopenapi.sock, whichhero_procdoesn't ship)/hero_proc/openrpc→ 200 (per-service explorer)/hero_proc/rpc→ 405 (POST-only, unchanged)Note for operators
The
hero_libgit dep was updated bycargo updateduring this work; the newherolib_corepanics on startup ifPATH_ROOTis unset. The hero_proc-supervisedhero_routerservice action did not inheritPATH_ROOTand now fails. Either setPATH_ROOTon the service action, or revert to the previously installed binary, until the runtime is updated to provide the env.