UNPKG

wcz-layout

Version:

112 lines (95 loc) 3.5 kB
--- name: client-db description: Querying data on client by creating reactive TanStack DB collections. --- ## Rules - 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. ## File Placement ``` 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); } ``` ## Next Step (ask user after completion) - Create a new Route