Frontend: split queries.ts by domain, extract error classes, add query-key factory; decide search-invalidation #65

Closed
opened 2026-06-08 13:42:12 +00:00 by logaritmisk · 1 comment
Owner

Severity: Medium. From a frontend deep audit, 2026-06-08. Structural cleanup of the data-access module.

Problems

  • web/src/api/queries.ts is 584 lines spanning 8 domains (error classes, auth, objects, fields-on-object, field-definitions, vocab, terms, authorities, search). No hook crosses a domain boundary, so the split is low-risk.
  • query-client.ts:9 imports HttpError/InUseError from queries.ts, dragging the whole hook module into the toast-wiring path.
  • No query-key factory — every key is a hand-written literal (["objects", params], ["object", id], ["terms", vocabularyId], …) across the module + config-provider.tsx:10. Collision-safe today but typo-prone.
  • Object writes don't invalidate ["search"]. useUpdateObject/useDeleteObject/useSetVisibility (queries.ts:205-208,250,413-416) invalidate ["objects"]/["object", id] but never the search index, so the search panel serves stale hits until refetch. Needs a deliberate decision (invalidate vs accept Meilisearch lag).

Suggested fix

Proposed layout:

src/api/
  client.ts            (unchanged)
  errors.ts            HttpError, FieldRejection, InUseError, VisibilityError
  query-keys.ts        typed key factory: keys.objects(p?), keys.object(id), keys.terms(vocabId), …
  queries/
    auth.ts            useMe, useLogin, useLogout
    objects.ts         useObjectsPage, useObject, useCreate/Update/DeleteObject, useSetFields, useSetVisibility
    field-defs.ts      useFieldDefinitions, useCreate/Update/DeleteFieldDefinition
    vocab.ts           useVocabularies, useCreate/Rename/DeleteVocabulary, useTerms, useAddTerm, useUpdate/DeleteTerm
    authorities.ts     useAuthorities, useCreate/Update/DeleteAuthority
    search.ts          useSearch, SEARCH_PAGE

Move error classes out first (decouples query-client.ts). Extract repeated boilerplate: unwrap(result, msg) for the if (error || !data) throw GET preamble (6×), and the 409 → InUseError block (4×). Then make an explicit call on the ["search"] invalidation gap (and document it).

Source: frontend deep audit (data-layer dimension), 2026-06-08. Note: the staleTime split (reference data 5min vs objects none) and the missing keepPreviousData on client-side-filtered reference lists are both intentional/correct — not part of this.

**Severity: Medium.** _From a frontend deep audit, 2026-06-08. Structural cleanup of the data-access module._ ## Problems - **`web/src/api/queries.ts` is 584 lines spanning 8 domains** (error classes, auth, objects, fields-on-object, field-definitions, vocab, terms, authorities, search). No hook crosses a domain boundary, so the split is low-risk. - **`query-client.ts:9` imports `HttpError`/`InUseError` from `queries.ts`**, dragging the whole hook module into the toast-wiring path. - **No query-key factory** — every key is a hand-written literal (`["objects", params]`, `["object", id]`, `["terms", vocabularyId]`, …) across the module + `config-provider.tsx:10`. Collision-safe today but typo-prone. - **Object writes don't invalidate `["search"]`.** `useUpdateObject`/`useDeleteObject`/`useSetVisibility` (`queries.ts:205-208,250,413-416`) invalidate `["objects"]`/`["object", id]` but never the search index, so the search panel serves stale hits until refetch. Needs a deliberate decision (invalidate vs accept Meilisearch lag). ## Suggested fix Proposed layout: ``` src/api/ client.ts (unchanged) errors.ts HttpError, FieldRejection, InUseError, VisibilityError query-keys.ts typed key factory: keys.objects(p?), keys.object(id), keys.terms(vocabId), … queries/ auth.ts useMe, useLogin, useLogout objects.ts useObjectsPage, useObject, useCreate/Update/DeleteObject, useSetFields, useSetVisibility field-defs.ts useFieldDefinitions, useCreate/Update/DeleteFieldDefinition vocab.ts useVocabularies, useCreate/Rename/DeleteVocabulary, useTerms, useAddTerm, useUpdate/DeleteTerm authorities.ts useAuthorities, useCreate/Update/DeleteAuthority search.ts useSearch, SEARCH_PAGE ``` Move error classes out first (decouples `query-client.ts`). Extract repeated boilerplate: `unwrap(result, msg)` for the `if (error || !data) throw` GET preamble (6×), and the `409 → InUseError` block (4×). Then make an explicit call on the `["search"]` invalidation gap (and document it). _Source: frontend deep audit (data-layer dimension), 2026-06-08. Note: the staleTime split (reference data 5min vs objects none) and the missing `keepPreviousData` on client-side-filtered reference lists are both intentional/correct — not part of this._
Author
Owner

Done in merge 002af9d.

  • api/errors.ts now owns the 4 error classes; error-message.ts imports them from ./errors, so the toast path (query-client → error-message → errors) no longer transitively loads the react-query hook module. The barrel re-exports the classes, so all existing importers are unchanged.
  • api/queries.ts (584 lines) → api/queries/{auth,objects,field-defs,vocab,authorities,search}.ts + index.ts barrel. All 27 hooks moved byte-for-byte; api/queries.ts deleted. The ../api/queries import path is unchanged for all ~30 consumers (resolves to the directory index) — zero consumer churn.
  • api/query-keys.ts central keys factory used by every queryKey/invalidate/setQueryData site, including config-provider's ["config"].
  • Search invalidation: useUpdateObject/useDeleteObject/useSetVisibility now invalidate ["search"] (no-op when not searching since the search query is term-gated) — the one intentional behavior change, with a focused test.

265 tests pass (existing suites unchanged); typecheck/lint/build clean; check:size 216.5 KB gz (≈unchanged); check:colors clean; no new dependency; no new i18n keys; no codename.

Deferred (cosmetic): repointing the 4 component error-importers from the ../api/queries re-export to ../api/errors directly.

Done in merge `002af9d`. - **`api/errors.ts`** now owns the 4 error classes; **`error-message.ts` imports them from `./errors`**, so the toast path (`query-client → error-message → errors`) no longer transitively loads the react-query hook module. The barrel re-exports the classes, so all existing importers are unchanged. - **`api/queries.ts` (584 lines) → `api/queries/{auth,objects,field-defs,vocab,authorities,search}.ts` + `index.ts`** barrel. All 27 hooks moved byte-for-byte; `api/queries.ts` deleted. The `../api/queries` import path is unchanged for all ~30 consumers (resolves to the directory index) — zero consumer churn. - **`api/query-keys.ts`** central `keys` factory used by every queryKey/invalidate/setQueryData site, including `config-provider`'s `["config"]`. - **Search invalidation:** `useUpdateObject`/`useDeleteObject`/`useSetVisibility` now invalidate `["search"]` (no-op when not searching since the search query is term-gated) — the one intentional behavior change, with a focused test. 265 tests pass (existing suites unchanged); typecheck/lint/build clean; check:size 216.5 KB gz (≈unchanged); check:colors clean; no new dependency; no new i18n keys; no codename. **Deferred (cosmetic):** repointing the 4 component error-importers from the `../api/queries` re-export to `../api/errors` directly.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logaritmisk/biggus-dickus#65