Skip to content

UN-4313 [FEAT] Add the v1 data layer: one HTTP client, server state and error contract - #2482

Draft
kirtimanmishrazipstack wants to merge 2 commits into
mainfrom
UN-4313-v1-data-layer
Draft

kirtimanmishrazipstack wants to merge 2 commits into
mainfrom
UN-4313-v1-data-layer

Conversation

@kirtimanmishrazipstack

@kirtimanmishrazipstack kirtimanmishrazipstack commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

What

  • Gives the new (v1) frontend one shared way to load and save data from the backend.
  • Every page now shows loading and errors the same way, so pages don't build their own.
  • The demo page uses it to show the organisation's LLM adapters.
  • An automatic check stops pages from calling the backend any other way.

Why

  • Without it, each page would handle loading and errors in its own way. That causes bugs and errors that users never see. Now every v1 page gets the same behaviour, and it's tested once.

How

  • services/http.ts is the only HTTP client, and builds the org-scoped paths (orgPath()). It:
    • adds X-CSRFToken to same-origin POST, PUT, PATCH and DELETE requests;
    • adds an X-Request-ID to every request;
    • rejects with an ApiError;
    • treats only the auth middleware's 401 as session expiry, and redirects once per page load, so concurrent 401s redirect once. A typed 401, such as a provider rejecting its key, is an ordinary client error.
  • Errors fall into nine kinds: validation, subscription, unauthenticated, forbidden, not_found, client, server, network, unknown.
    • mapFieldErrors() turns errors for fields the form doesn't show into one form-level error, and gives nothing before the first submit.
  • In the TanStack Query defaults (app/query.ts):
    • reads retry twice, and only on network, 502, 503 or 504 errors;
    • mutations never retry;
    • networkMode: "always", so offline fails at once instead of pausing, and a save is never sent later;
    • each failure is reported once: inline by QueryView when the first fetch fails, otherwise as a toast;
    • query keys start with the org id, and each query fetches for the org in its key.
  • lint:v1 rejects backend URLs, axios, the client and fetch / XMLHttpRequest / EventSource outside services/ (and a cloud plugin's plugins/<plugin>/services/).
  • The rules for writing service modules are in services/README.md.
  • To run the v1 tests on Node 26, set NODE_OPTIONS=--no-experimental-webstorage. CI uses Node 22 and needs nothing extra.

Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)

  • No. It only changes the new v1 frontend, which users see only after switching to it. The current frontend is untouched.

Database Migrations

  • None

Env Config

  • None

Relevant Docs

Related Issues or PRs

  • UN-4313
  • UN-4314 (sign-in session) will tell this code which organisation the user is in. Until then, the demo page takes it from the page address, which doesn't work on cloud. UN-4314 also moves sign-out on session expiry to the POST logout, since the GET one goes when v0 is retired.
  • UN-4290 [FEAT] Add the v1 account menu and organisation switcher #2395 adds a temporary copy of this client. Whichever PR merges second moves that code onto this one. This is still pending.

Dependencies Versions

  • None

Notes on Testing

  • The new tests, the existing frontend tests and the production build all pass.
  • The tests cover:
    • the security token sent with saves;
    • signing the user out when their session ends;
    • requests cancelled when a page closes;
    • each kind of error.
  • Tried against a local backend in a browser:
    • the demo page lists the organisation's LLM adapters;
    • when the session ends, the user goes to the sign-in page once;
    • an error from a provider or a connector doesn't sign the user out;
    • offline, the page shows an error instead of loading forever, and a save fails instead of being sent later.

Screenshots

Checklist

I have read and understood the Contribution Guidelines.

🤖 Generated with Claude Code

…nd error contract

- services/http.ts: one axios client; CSRF on same-origin mutating requests, a request id on every request, ApiError rejections, one redirect for concurrent 401s.
- services/context.ts: the org id and session-expiry hook UN-4314 sets.
- services/errors.ts and report.ts: one error shape, form field mapping, one reporter; app/query.ts wires it into the v1 QueryClient.
- QueryView pattern, page contract, sample adapters service on the demo page.
- lint:v1 rejects backend URLs, axios and the client outside services/.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

@kirtimanmishrazipstack kirtimanmishrazipstack left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Standardized review (unstract v2.1.80): REQUEST CHANGES

Summary: Critical: 0 · High: 2 · Medium: 7 · Low: 8 · Lenses run: 19/19 (15 examined, 4 N/A)

  • Security deep-dive: done, no findings at confidence >= 8.
  • Boundary sweep: 7 sinks traced, clean.
  • test:v1 passes on Node 26 (720 tests, 182/182 guardrail fixtures).
  • lint:v1, typecheck:v1 and Biome 2.3.13 are clean on the changed files.

Lens checklist

  1. Spec & intent: See findings #4
  2. Architectural fit: See findings #7, #15, #17
  3. Correctness: See findings #1, #6, #10
  4. Security: Clean
  5. Data integrity: N/A (frontend only)
  6. Concurrency: See findings #5, #7
  7. API & contract: See findings #3, #11
  8. Reliability: See findings #6
  9. Performance: Clean
  10. Observability: Clean
  11. Operational safety: Clean (v1 only, behind the version switch)
  12. LLM/agent: N/A
  13. Testing: See findings #2, #8, #16
  14. Dependencies: Clean (no manifest change)
  15. Code quality: See findings #15
  16. Doc accuracy: See findings #1, #5, #12, #13, #14
  17. Cross-document: See findings #9
  18. On-prem: N/A
  19. Scaling: N/A

Open questions

  • #5: must UN-4314's expiry handler leave the page, or should the latch re-arm?
  • #3: for typed 401s, a client-side check or a backend remap of SdkError 401?
  • v0 sends subscription_error to /subscription-expired, but v1 shows a toast. Is that intended?
  • The defaults keep staleTime: 0 with refetch on focus, so every mounted query refetches on each tab focus. Is that the default you want?
  • A page past the end returns 404 "Invalid page.". Should paged queries step back a page?

Unanchored findings

  • frontend/src/v1/test/README.md:153 (outside the diff hunk): 9. [Medium] [Lens 17] — This fixture example contradicts the real test/fixtures/adapters.ts

The real file this PR adds has adapterFixtures.llm and adapterHandlers(session, rows, seen), and the new Adapter type needs more fields than this example has (satisfies would fail). L110 uses adapterFixtures.list, which doesn't exist. Suggest deleting the block, pointing to the real file, and fixing L110. Made stale by test/fixtures/adapters.ts:L6.

  • frontend/src/v1/README.md:143 (outside the diff hunk): 13. [Low] [Lens 16] — A 401 from a service call now redirects to sign in, so "no … login redirect" is out of date.

Comment thread frontend/src/v1/services/errors.ts Resolved
Comment thread frontend/src/v1/services/errors.ts Resolved
Comment thread frontend/scripts/check-v1.mjs Resolved
Comment thread frontend/src/v1/services/http.ts Resolved
Comment thread frontend/src/v1/services/README.md Resolved
Comment thread frontend/src/v1/services/adapters.ts Resolved
Comment thread frontend/src/v1/services/http.ts Resolved
Comment thread frontend/src/v1/app/query.test.tsx Resolved
Comment thread frontend/scripts/check-v1.mjs Resolved
…e, org-scoped queries, stricter lint

- A typed 401 (a provider's key, a connector's OAuth) is a client error; only the auth middleware's 401 expires the session.
- toApiError calls a cancel unknown, not network; mapFieldErrors gives nothing for no error or a cancel.
- QueryView shows the alert only when the first fetch failed; a failed refresh keeps the data.
- networkMode always: offline fails as network instead of pausing, and a save is never sent later.
- Queries fetch for the org in their key; orgPath moves into http.ts with an org option.
- lint:v1 matches the client under any spelling and flags fetch, XMLHttpRequest and EventSource outside services/; plugins/<plugin>/services/ may hold them.
- Drop the unused query-level meta.notify; document the once-per-page-load expiry and fix the README rows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@sonarqubecloud

Copy link
Copy Markdown

@kirtimanmishrazipstack

Copy link
Copy Markdown
Contributor Author

Answers to the findings and questions in the review body. The fixes are in 74db2e1.

Open questions:

  • Add support for configuring and using OCR adapters #5 and connectors #3: answered in their threads. The expiry handler must leave the page, and typed 401s are told apart on the client.
  • v0's /subscription-expired redirect: v1 not redirecting is intended for now. Plan gates and their redirects belong to UN-4314 (P0-T06: session, guards and plan gates). Until then, a subscription error is a toast that stays until dismissed.
  • staleTime: 0 with refetch on focus: keeping TanStack's defaults. Other members change an organisation's data, so returning to the tab should show their changes, and a page mounts only a few queries. A costly or polling query sets its own staleTime.
  • A page past the end (404 "Invalid page."): the data layer can't change the page number, because it's part of the query key. Whoever holds the page number steps back to the last page (count) after a delete. services/README.md now says so under pagination.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant