Skip to content

Latest commit

 

History

History
42 lines (34 loc) · 4.91 KB

File metadata and controls

42 lines (34 loc) · 4.91 KB
name project-mtp-data-layer
description mtp-web data layer — fetch through the demo-API seam (apiGet), fixtures served by MSW (visible in Network tab), never hardcode data in data/ or pages; TanStack Query + query-key-factory for server state; one Zustand app store for client/UI state. Pin to ADR-0011.
metadata
type
project

For mtp-web. Authoritative: adr/0011-data-layer-demo-api-query-store.html.

The real mtp-api (.NET) isn't built yet, so screens run on demo APIs served by MSWapiGet always does a real fetch, and a Mock Service Worker intercepts it in dev and returns dummy JSON (calls show in the DevTools Network tab as real HTTP 200s). Do not hardcode data in pages or in the query hook — fixtures live only in apps/web/src/mocks/.

API hard rules (non-negotiable)

  1. Filtering / search / pagination is ALWAYS a POST with a body — never GET query params. Use apiSearch<T>(path, filters) (POST) for any filtered/searched/paginated read. apiGet<T>(path) takes no params by design and is only for by-key / static resource reads. Endpoint convention: POST /<module>/<resource>/search. The query key must include the filter object so each filter combination caches separately, e.g. useQuery({ queryKey: [...financeKeys.salesRegister.queryKey, filters], queryFn: () => apiSearch<SrData>("/accounting/salesRegister/search", filters) }).
  2. Route names are ALWAYS camelCase — both API endpoints AND frontend route segments. Multi-word segments use camelCase: API /accounting/salesRegister/search, /finance/bankReconciliation; frontend URLs /finance/bankRecon, /finance/costCentre, /finance/ccReport (the pageId in nav.ts ids + App.tsx pages keys). Never kebab-case, snake_case, or lowercase-smushed. (Single words / acronyms like tds, gst, ledger, bs, pl are already fine.)
  3. Selection controls submit the id, never the label. Combos/dropdowns use { value, label } (ComboboxItem adds an optional hint): value = id/code, label = display name. RHF stores and the API receives the value (e.g. account code 4001-HDF7710, cost-centre CC-SAL-01) — never the label. Multi-selects submit an array of ids. Server responses for edit forms therefore return ids; resolve the label via the same search/lookup endpoint (Combobox displayValue seeds it).

How to build a new screen's data:

  1. Types + hook in apps/web/src/data/<screen>.ts — no data:
    export function useXxx() {
      return useQuery({ ...moduleKeys.xxx, queryFn: () => apiGet<XxxData>("/module/xxx") });
    }
  2. Fixture + MSW handler in apps/web/src/mocks/<screen>.mock.ts — the dummy JSON + an exported handler array; then add its spread to mocks/handlers.ts:
    import { http, HttpResponse, delay } from "msw";
    export const xxxHandlers = [
      http.get("/api/module/xxx", async () => { await delay(120); return HttpResponse.json(data); }),
    ];                                                          // path may carry :params, e.g. /api/finance/accounts/:id/ledger
  3. Query key in apps/web/src/keys/<module>.keys.ts via createQueryKeys (@lukemorales/query-key-factory). Leaf = xxx: null; dynamic = xxx: (id) => ({ queryKey: [id] }).
  4. Server state → TanStack Query only. Client/UI state → the one Zustand store (stores/appStore.ts, ui + session slices; persist only ui prefs). Ephemeral screen state stays useState or the URL (?tab=). No feature store unless 2+ components share non-server, non-URL state.

Key facts:

  • apiGet/apiPost/apiPut/apiDelete (lib/api/client.ts) are the only fetch entry points and always do a real fetch (Authorization: Bearer + X-Client-Code headers, RFC 7807 ProblemDetailsApiError). In dev (default, VITE_API_MODE unset) MSW intercepts and returns the fixture; calls appear in the DevTools Network tab. main.tsx starts the worker (mocks/browser.ts) before first render. Flipping to the real API is one env flag (VITE_API_MODE=live — worker never starts) — hooks/pages never change.
  • The MSW service worker is public/mockServiceWorker.js (regenerate with npx msw init public if it goes missing).
  • Because the handler awaits, loading/empty/error states are real — handle them (don't assume instant data; if (!data) return null flashes briefly).
  • All 17 built accounting screens now fetch via MSW (apiGet + financeKeys leaf; fixtures in mocks/*.mock.ts, handlers in mocks/handlers.ts) and show a skeleton on isPending. Migrated 2026-05-23. New screens follow the same pattern from the start. Still pending (separate, smaller scope): converting the few input-bearing screens' hand-rolled inputs to the RHF field kit (ADR-0017) and hand-rolled tables to GridTable — Voucher Entry (static mockup), TDS challan grid, Bank Recon.

Related: [[project-mtp-component-authoring]], [[project-mtp-stack]].