XYLEX Group
DevelopmentResource Framework

Api Client

API client — fetching and mutations

The resource framework ships a thin client hook useApiClient used by both list and drilldown views. It standardizes headers, optional caching, and request/response shapes to the suitsbooks API.

Hook signature

function useApiClient<T>({
  table,
  conditions,
  columns,
  limit,
  offset,
  enabled,
  noCache,
  single,
  schema,
}: {
  table: string;
  conditions?: Array<{ eq_column: string; eq_value: string | number | boolean }>;
  columns?: string[];
  limit?: number;
  offset?: number;
  enabled?: boolean;
  noCache?: boolean;
  single?: boolean;
  schema?: string; // default: "public"
}): {
  data: T | T[] | null;
  isLoading: boolean;
  isError: boolean;
  error: string | null;
  cacheKey: string | null;
  mutate(): Promise<void>;
  insert(row: Partial<T>): Promise<T>;
  insertMany(rows: Partial<T>[]): Promise<T[]>;
  update(idColumn: string, id: string | number, updateBody: Record<string, any>): Promise<T>;
  remove(idColumn: string, id: string | number, updateBody: Record<string, any>): Promise<T>;
};
```typescript

### Requests

- Fetch: `POST {APP_CONFIG.api.suitsbooks}/fetch/data`
  - Headers: `X-Company-Id`, `X-Organization-Id`, `X-User-Id`, `Content-Type`, optionally `Cache-Control: no-cache`
  - Body:
    - `table_name` (string)
    - `schema` (string, optional)
    - `conditions` (array of `{ eq_column, eq_value }`)
    - `columns` (optional string array)
    - `limit` (number), `offset` (number)

Caching header:

- Sent only when `noCache === true`.
- In list/drilldown, `noCache` is resolved from `force_no_cache` (per‑resource) and/or user scope (see caching.md).

### Responses (expected)

All endpoints return JSON. For fetch:

```json
{
  "data": [ /* rows */ ],
  "cache_key": "optional-cache-key"
}
```typescript

Notes:

- `data` can be an array or an object. The hook normalizes to an array; if `single: true`, the first item is returned as `T`.
- `cache_key` (or `cacheKey`) is captured when present for diagnostics.
- Non‑OK responses include an error message in the body where possible; the hook maps it into `error`.

### Example: company settings (allowed import objects)

Use the hook instead of hand‑rolled `fetch` to load company settings and drive UI flags like allowed import objects.

```typescript
const { data: companySettings } = useApiClient<any[]>({
  table: "company_settings",
  conditions: [{ eq_column: "company_id", eq_value: user?.company_id || "" }],
  enabled: Boolean(user?.company_id),
  noCache: !cacheEnabled,
  single: false,
});

useEffect(() => {
  const rows = Array.isArray(companySettings)
    ? companySettings
    : companySettings
      ? [companySettings as any]
      : [];
  const first = rows[0];
  if (first && Array.isArray(first.allowed_import_objects)) {
    setAllowedTables(first.allowed_import_objects);
  }
}, [companySettings]);
```typescript

### Mutations

- Insert (single): `PUT {APP_CONFIG.api.suitsbooks}/data/insert`
  - Body: `{ table_name, schema?, insert_body: { ... } }`
- Insert (bulk): same endpoint with `insert_body: rows[]`
- Update: `PUT {APP_CONFIG.api.suitsbooks}/update/data`
  - Body: `{ table_name, schema?, x_column, x_id, update_body }`
- Remove: wrapper over `update/data` that sets `awaiting_archival: true` by convention, plus any extra `updateBody`

Return shape:

```json
{ "data": { /* inserted/updated row */ } }
```typescript

The hook re-fetches (`mutate()`) after successful mutations.

### Conditions

Conditions are provided as an array of equality filters:

```typescript
conditions: [
  { eq_column: "company_id", eq_value: user.company_id },
  { eq_column: "invoice_id", eq_value: id },
];
```typescript

Use the URL’s query parameters for ad‑hoc client‑side filtering in list views (see `ResourceTable` advanced filters).

### Error handling

- Network or server errors set `isError: true` and a human‑readable `error` string.
- The hook does not throw; consuming components can show an inline error state.

### Tips

- Provide `columns` to reduce payload size; the table/drilldown will add the id/avatar columns as needed.
- Use `single: true` for drilldowns to fetch just one row and simplify state updates.
- When integrating with database‑driven routes, pass `schema` from the mapped `ResourceRoute` if provided.
*** End Patch