Skip to content

The Studio (apps/stream) gets its own generated API client and JWT auth, not @repo/api's

frontend/apps/stream/ already depended on @repo/api (Orval-generated hooks shared with apps/web), but that package's mutator (authedInstance in frontend/packages/api/src/mutator.ts) calls apiProxy, a TanStack Start server function. apps/stream is a plain Vite SPA with no SSR server — that import cannot resolve there, so none of the existing generated hooks (including the standalone-media ones like useApiV1VideosCreate) actually work inside it today.

We decided the Studio needs its own Orval client: a second orval.config.ts (same OpenAPI schema as source) generating into its own package/output, paired with its own mutator that attaches a JWT Bearer token directly, with no server-side proxy involved.

Considered Options

  • Plain fetch, no Orval (mirror apps/admin's apiClient.ts/useDataTable pattern): simplest, zero new infra, but throws away type safety and gives up the schema-driven contract that catches drift when the backend changes.
  • Make @repo/api's mutator pluggable (swap apiProxy for a fetch+Bearer implementation depending on which app calls it): keeps one generated client, but couples apps/web's server-function-based auth model to a second, incompatible one in the same file — risks regressing apps/web for the sake of an app with fundamentally different auth.
  • Separate Orval client for the Studio (chosen): own orval.config.ts, own generated output, own mutator. Keeps type-safety/schema-drift protection for both apps without forcing them to share an auth model that doesn't fit both.

Why this was chosen

  • apps/stream's Studio is standalone by design (ADR-0009's Standalone Media, extended to the full pipeline) — it authenticates a CustomUser directly with no Community/Membership context, unlike every other Orval consumer in the repo.
  • Auth mirrors apps/admin's existing, working pattern: JWT (access + refresh) in localStorage, Authorization: Bearer attached per request, refresh-on-401 against /api/v1/auth/token/refresh/. No SSR, no session held server-side.
  • The two mutators solve genuinely different problems (server-held session vs. client-held JWT) — collapsing them into one pluggable mutator would make frontend/packages/api/src/mutator.ts harder to reason about for no shared benefit.

Consequences

  • Two Orval configs now read the same openapi.json and must both be regenerated (make generate-api needs to cover both, or a second script) whenever the backend schema changes — a schema change affecting standalone-media endpoints needs both clients regenerated, not just one.
  • The Studio holds a JWT in browser storage (unlike apps/web, which never does — see mutator.ts's comment). This is an accepted, scoped exception: same trade-off apps/admin already made, not a new risk pattern.
  • frontend/apps/stream/package.json's existing @repo/api dependency becomes dead weight for the Studio's own calls (it may still be useful if the Embed player wants a typed client for the public PublicMediaDetailView response) and should be re-evaluated once the new client lands.

Strum — Documentação.