---
name: client-db
description: "Use when: creating or modifying TanStack DB collections, live queries, optimistic collection writes, subset loading, collection preloading."
---

## Rules

- Use `eager` syncMode for top-level collections, `on-demand` for child/relational data.
- Use `useLiveQuery` for list/grid pages and `useLiveSuspenseQuery` for detail components wrapped in suspense.
- For mutations use `createOptimisticAction` — mirror the server mutation in `onMutate`, then call the server function and `collection.utils.refetch()` in `mutationFn`.
- Use `meta.loadSubsetOptions` for relational subset loading.
- Use Axios with auth interceptor `getAccessToken` but only for public REST APIs; use default `api` as a scope key.

## File Placement

```
src/db-collections/           — DB collections
src/server/actions/           — server functions (select/update/insert/delete)
src/lib/schemas/              — Zod schemas (shared between client and server)
src/lib/auth/scopes.ts        — API scope keys
wcz-layout/data               — queryClient instance
wcz-layout/utils              — getAccessToken utility function
```

## Examples

```ts
// src/db-collections/<feature>.ts
export const api = axios.create({
  baseURL: "/api/libraries",
});

api.interceptors.request.use(async (config) => {
  const accessToken = await getAccessToken("api");
  config.headers.set("Authorization", `Bearer ${accessToken}`);
  return config;
});

// src/db-collections/<feature>.ts
export const librariesCollection = createCollection(
  queryCollectionOptions({
    queryKey: ["libraries"],
    queryFn: () => selectLibraries(),
    getKey: ({ id }) => id,
    schema: LibrarySchema,
    queryClient: queryClient,
    syncMode: "eager",
  }),
);

// src/db-collections/<feature>.ts
export const bookCollection = createCollection(
  queryCollectionOptions({
    queryKey: ["books"],
    queryFn: ({ meta }) => selectBooks({ data: meta?.loadSubsetOptions }),
    getKey: ({ id }) => id,
    schema: BookSchema,
    queryClient: queryClient,
    syncMode: "on-demand",
  }),
);

// simple query
const { data, isLoading } = useLiveQuery((q) =>
  q.from({ library: libraryCollection }).orderBy(({ library }) => library.name, "asc"),
);

// advanced query with relational data
const { data } = useLiveQuery((q) =>
  q
    .from({ library: libraryCollection })
    .where(({ library }) => eq(library.id, id))
    .findOne()
    .select(({ library }) => ({
      ...library,
      books: toArray(
        q
          .from({ book: bookCollection })
          .where(({ book }) => eq(book.libraryId, library.id))
          .orderBy(({ book }) => book.title, "asc")
          .select(({ book }) => ({
            id: book.id,
            title: book.title,
          })),
      ),
    })),
);

// form submission handler
const handleOnSubmit = createOptimisticAction<Library>({
  onMutate: (formValues) => {
    libraryCollection.update(id, (prev) => Object.assign(prev, formValues));
  },
  mutationFn: async (formValues) => {
    await updateLibrary({ data: formValues });
    await libraryCollection.utils.refetch();
  },
});

// calling the handler
try {
  const transaction = handleOnSubmit(formValues);
  await transaction.isPersisted.promise;
} catch (error) {
  if (error instanceof Error) alert(error.message);
}
```
