wcz-layout
Version:
112 lines (95 loc) • 3.5 kB
Markdown
---
name: client-db
description: Querying data on client by creating reactive TanStack DB collections.
---
- Use `eager` syncMode for top-level collections, `on-demand` for child/relational data.
- Use `useLiveQuery` on list pages (provides `isLoading` for DataGrid), `useLiveSuspenseQuery` on detail/edit pages or components (wrap in `<Suspense>` with loading fallback).
- 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.
```
src/db-collections/ — DB collections
src/server/actions/ — server functions (select/update/insert/delete)
src/lib/schemas/ — Zod schemas
src/lib/auth/scopes.ts — API scope keys
wcz-layout/data — shared data utilities and server functions from npm package
wcz-layout/utils — shared utils from npm package
```
## Examples
```ts
// imports
import { queryClient } from "wcz-layout/data";
import { getAccessToken } from "wcz-layout/utils";
// src/db-collections/library.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;
});
export const librariesCollection = createCollection(
queryCollectionOptions({
queryKey: ["libraries"],
queryFn: () => selectLibraries(),
getKey: ({ id }) => id,
schema: LibrarySchema,
queryClient: queryClient,
syncMode: "eager",
}),
);
// src/db-collections/book.ts
export const bookCollection = createCollection(
queryCollectionOptions({
queryKey: ["books"],
queryFn: ({ meta }) => selectBooks({ data: meta?.loadSubsetOptions }),
getKey: ({ id }) => id,
schema: BookSchema,
queryClient: queryClient,
syncMode: "on-demand",
}),
);
// src/routes/libraries/edit/$id.tsx
const { data, isLoading } = useLiveQuery((q) =>
q.from({ library: libraryCollection }).orderBy(({ library }) => library.name, "asc"),
);
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,
})),
),
})),
);
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();
},
});
try {
const transaction = handleOnSubmit(formValues);
await transaction.isPersisted.promise;
} catch (error) {
if (error instanceof Error) alert(error.message);
}
```
- Create a new Route