---
name: catalyst-data-layer
description: >
  Wire Angular components, router resolvers, and ad-hoc code to the Aurora Catalyst GraphQL
  data-access layer (`@aurora/modules/graphql`). Trigger: ANY time you are about to write code that
  reads or mutates server data — composing a list or detail screen, writing a `ResolveFn`, adding a
  custom query or mutation, picking between a fetcher (one-shot) and a composable (stateful). Also
  fires when debugging Apollo / signal mismatches, when the user mentions "query", "mutation",
  "resolver", "composable", "GraphQL", "Apollo", "useGraphql", or any `@aurora/modules/graphql`
  import. Skip if the task is pure UI composition with no data access — use
  `catalyst-component-catalog` instead.
license: MIT
metadata:
  author: aurora
  version: '1.0'
---

## When to Use

- Wiring a list view to paginated data (component + resolver + data-table).
- Wiring a detail view that loads, creates, and updates a single record.
- Writing a `ResolveFn` that pre-loads data for a route.
- Adding a custom query / mutation that is not covered by the generated configs.
- Answering "how do I read / write data from this component?".
- Debugging why a composable-in-resolver call creates dead signals, or why `runInInjectionContext`
  is needed in a composable method.

**Skip this skill** if the task is pure UI composition with no data access (no Apollo, no
`useGraphql*`, no `query*` / `mutate*` fetcher). Use `catalyst-component-catalog` for that.

## Mandatory Discovery Protocol

BEFORE writing any code that calls `@aurora/modules/graphql`, follow this protocol. It keeps you in
sync with the actual code instead of memorizing a list that ages.

1. **Read the two index files to learn what exists**
   - `Read frontend/src/@aurora/modules/graphql/fetchers/index.ts` — the pure call layer.
   - `Read frontend/src/@aurora/modules/graphql/composables/index.ts` — the stateful layer.
2. **Open the specific file you need**
   - If you need a fetcher (one-shot): `Read frontend/src/@aurora/modules/graphql/fetchers/<name>.ts`
     for the signature, parameter names, and return type.
   - If you need a composable (component-scoped): `Read frontend/src/@aurora/modules/graphql/composables/<name>.ts`
     for the returned shape (signals, methods, loading flag).
3. **Consume, do NOT re-derive**
   - Lift the signature from the file you opened. Do not guess parameter order or keys from
     memory.
   - Do not re-implement a call body "just to be safe". If a fetcher covers the need, use it.

This protocol is also how you stay correct when new fetchers or composables are added — the skill
does not need to be updated, the indexes are the catalog.

## Conceptual Hierarchy

The data-access layer is split in two sibling folders under `@aurora/modules/graphql/`:

```
fetchers/                  "how" — the call to the GraphQL server
  queryX<T>(...)           reads  → Observable<T | T[] | DataTableData<T> | null>
  mutateX(...)             writes → Observable<...>
                           Pure functions. No signals. Only `inject(Apollo)`.
                           Safe in any injection context (component, resolver, guard).

composables/               "where" — stateful wrappers for component lifecycle
  useGraphqlX(...)         wrap their matching fetcher AND add signals.
                           Return { method, signal-projections, loading }.
                           Designed for component bodies.
```

The folder name `fetchers/` describes the technical role (a call to the server, like `fetch()` in
the JS API: covers both GET and POST). The function names use GraphQL vocabulary (`query*` for
reads, `mutate*` for writes). The composables prefix `useGraphql*` follows the Angular / React
convention for lifecycle-aware helpers.

### When to use which layer

| Consumer                    | Use                        | Why                                                   |
| --------------------------- | -------------------------- | ----------------------------------------------------- |
| Component (list / detail)   | Composable (`useGraphqlX`) | You want the signals for template reactivity.         |
| Router resolver (`ResolveFn`) | Fetcher (`queryX`)         | One-shot fetch; resolver has no lifecycle for signals.|
| Service, guard, interceptor | Fetcher                    | Same as resolver — no component to hold state.        |
| Custom Apollo cache control | `inject(Apollo)` directly  | Only when a fetcher / composable does not fit.        |

### Internal relationship

Composables delegate their call to the matching fetcher, wrapped in `runInInjectionContext` so the
inner `inject(Apollo)` resolves correctly when the composable method is invoked from a template
event (click, submit, etc.):

```typescript
// the shape every primitive composable follows internally
const injector = inject(Injector);
// ...
return runInInjectionContext(injector, () => queryX<T>(statement, key, fields, variables))
  .pipe(tap((data) => { /* write to signals */ }));
```

If you are extending a composable, keep that wrapper. If you are reading composable code and
wonder why `runInInjectionContext` appears there, this is why: the fetcher uses `inject(Apollo)`,
and the method that calls it may fire outside the composable's constructor-time injection context.

## Patterns

All four patterns below have anchored, executable examples in
[`references/known-patterns.md`](references/known-patterns.md). Read that file when you need to
copy a structure.

### 1. List pattern — component with facade

**Shape**:

```
ResolveFn (queryPaginate)  →  route.data['pagination']  →  Component
                                                            ├── useGraphqlList(CONFIG)
                                                            ├── useDataTable(columns, data)
                                                            └── buildSearch/Filter/SortWhere(...) for refetch
```

- Consume `useGraphqlList<Entity>(CONFIG)` to get `{ paginate, pagination, deleteById, loading }`.
- Pre-load the first page via the resolver (fetcher), so the component opens with data already in
  `route.snapshot.data[...]`. Store it in a local `page` signal.
- Use `useDataTable({ data, columns, manualSorting, manualPagination, onSortingChange,
  onPaginationChange })` to get the TanStack Table instance.
- On sort / filter / search changes, call `list.paginate({ query: { where, order, offset, limit }
  })` and push the result into the local `page` signal.
- For `where` / `order`, use the builders `buildSearchWhere / buildFilterWhere / buildSortOrder`
  from `@aurora/components/data-table/lib/` (see cross-reference below).

### 2. Detail pattern — component with facade + pickModel

**Shape**:

```
ResolveFn (queryFindById)  →  route.data['item']  →  Component
                                                      ├── useGraphqlDetail(CONFIG)
                                                      ├── pickModel(item, defaults)  → initial form values
                                                      └── FormBuilder.group(...)      → signalForm
```

- Consume `useGraphqlDetail<Entity>(CONFIG)` to get `{ findById, item, create, update, lastPayload, loading }`.
- The resolver pre-loads the item (fetcher) so the component receives it via
  `route.snapshot.data['item']`.
- Use `pickModel(item, defaults)` to project the loaded entity into the exact shape the form
  needs — this never leaks extra properties and uses the defaults for missing keys. The defaults
  object doubles as documentation of the form's shape.
- Build the reactive form with `FormBuilder.group({ ... })` using the pickModel result as initial
  values.
- On submit, call `detail.create(...)` or `detail.update(...)` depending on mode.

### 3. Resolver pattern — fetchers with config destructuring

**Shape**:

```
ResolveFn → destructure { queryX, keyX, fields } from CONFIG → queryPaginate / queryFindById / ...
```

- Resolvers MUST use fetchers, not composables. Signals do not belong here.
- Always destructure from the module's generated `*_LIST_CONFIG` / `*_DETAIL_CONFIG` so the query,
  key, and fields are the single source of truth shared with the component.
- For paginated list pre-loads without filters, pass a minimal variables object:
  `{ query: { offset: 0, limit: DEFAULT_PAGE_SIZE, order: buildSortOrder([]) } }`.
- Return the `Observable` directly — the router awaits it and exposes the value under
  `route.snapshot.data[...]`.

### 4. Escape hatch — `inject(Apollo)` directly

This is reserved for the rare case that neither a fetcher nor a composable fits. Examples:

- You need explicit Apollo cache policy tuning (`fetchPolicy`, `nextFetchPolicy`) that is not yet
  exposed through the fetchers.
- You need `watchQuery` with a subscription that updates reactively as the cache changes, not a
  one-shot `query`.
- You are implementing a new fetcher or composable and are by definition underneath the layer.

**Before you reach for `inject(Apollo)`**, verify no fetcher covers your case — check
`fetchers/index.ts`. The escape hatch is the exception, not the default.

## QueryStatement builders — cross-reference

The helpers `buildSearchWhere`, `buildFilterWhere`, and `buildSortOrder` live under
`@aurora/components/data-table/lib/` and are fully documented in the
[`catalyst-data-table-management`](../catalyst-data-table-management/SKILL.md) skill. They produce
Sequelize-compatible `where` / `order` objects that pass straight into a fetcher's `variables.query`.

- **In a resolver**: use `buildSortOrder([])` to produce the default empty ordering for the
  initial list pre-load. No other builder is needed there.
- **In a list component**: use all three to translate the current TanStack state (sorting,
  filters, search) into the `paginate()` variables on refetch.

Do NOT duplicate the builders' documentation here — consult the data-table skill.

## Anti-patterns

Three smells appear in AI-generated code because the correct shape is non-obvious. Each one has a
prescribed alternative:

### ❌ `HttpClient` for data access
Use Apollo. The project is exclusively GraphQL. If you reach for `HttpClient`, you are bypassing
the caching, typing, and error-handling that the Apollo layer provides. The composable / fetcher
you want probably already exists; re-check `fetchers/index.ts` and `composables/index.ts`.

### ❌ `BehaviorSubject` (or any RxJS Subject) for state
Use `signal()` from `@angular/core`. The project follows the signals-first rule from
`frontend/CLAUDE.md`. Mixing RxJS Subjects with signals fragments the change-detection model and
breaks downstream `computed()` derivations.

### ❌ GraphQL operator string literals like `'[iLike]'` or `'[or]'`
Use the `Operator` enum from `@aurorajs.dev/core-common`:

```typescript
import { Operator } from '@aurorajs.dev/core-common';

// ✅ correct
return {
  [Operator.or]: columns.map((col) => ({
    [col]: { [Operator.iLike]: `%${value}%` },
  })),
};

// ❌ anti-pattern — compiles, but drifts if the serialization changes
return {
  '[or]': columns.map((col) => ({
    [col]: { '[iLike]': `%${value}%` },
  })),
};
```

The string literals compile today because the enum serializes to the same bracketed form, but they
silently break if the enum's internal representation ever changes.

## Related skills

- [`catalyst-data-table-management`](../catalyst-data-table-management/SKILL.md) — column
  definitions, TanStack table wiring, and the full contract of `buildSearchWhere` /
  `buildFilterWhere` / `buildSortOrder`.
- [`catalyst-form-composer-auditor`](../catalyst-form-composer-auditor/SKILL.md) — compose or
  audit the body of a detail form after `pickModel` has given you the initial shape.
- [`catalyst-component-catalog`](../catalyst-component-catalog/SKILL.md) — the UI side; the
  visual components you render around the data.
