examples/new/.opencode/skills/zeus-graphql/SKILL.md
Zeus type-safe GraphQL client with React Query integration - useQuery/useMutation + Zeus queryFn, selectors, cache invalidation, API layer architecture
npx skillsauth add aexol-studio/axolotl zeus-graphqlInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
React Query (state/cache) -> Zeus api layer (type-safe) -> GraphQL server
api/client.ts, api/query.ts, api/mutation.ts — type-safe GraphQL communicationuseQuery/useMutation which call Zeus internally// api/client.ts
import { Chain } from '../zeus/index';
export const createChain = () =>
Chain('/graphql', { headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin' });
// api/query.ts
import { createChain } from './client';
export const query = () => createChain()('query');
// api/mutation.ts
import { createChain } from './client';
export const mutation = () => createChain()('mutation');
import { Selector, type FromSelector } from '../zeus/index.js';
const postSelector = Selector('Post')({ _id: true, title: true, content: true, published: true });
type PostType = FromSelector<typeof postSelector, 'Post'>; // derive type — never duplicate manually
api/selectors.ts, re-export from api/index.ts$ VariablesUse $ when values come from user input or props:
import { $ } from '../zeus/index';
await mutation()(
{ login: [{ email: $('email', 'String!'), password: $('password', 'String!') }, true] },
{ variables: { email, password } },
);
⚠️ Use
isLoading, NOTisPending.isPendingistruewhenenabled: false, causing permanent loading states.isLoading(isPending && isFetching) only fires during actual fetches.
import { useQuery } from '@tanstack/react-query';
import { query } from '../api';
import { queryKeys } from '../lib/queryKeys';
import { useAuth } from '../hooks';
const { isAuthenticated } = useAuth();
const { data, isLoading, error } = useQuery({
queryKey: queryKeys.posts, // define in queryKeys.ts first
queryFn: async () => {
const data = await query()({ user: { posts: { _id: true, title: true } } });
return data.user?.posts ?? [];
},
enabled: isAuthenticated,
});
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { mutation } from '../api';
import { queryKeys } from '@/lib/queryKeys.js';
const queryClient = useQueryClient();
const createPost = useMutation({
mutationFn: async (input: { title: string; content: string }) => {
await mutation()({ user: { createPost: [input, true] } });
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.posts });
},
});
queryClient.invalidateQueries({ queryKey: queryKeys.posts })login/register): for queryKeys.me, run explicit await queryClient.fetchQuery({ queryKey: queryKeys.me, queryFn: ... }) sync after success; do not rely on invalidation aloneinvalidateQueries callqueryClient.clear() (clears ALL cache — security)useState/useEffect fetchingquery()/mutation() go inside queryFn/mutationFn onlyenabled for conditional queries (e.g., enabled: isAuthenticated)FromSelector to derive types — never duplicate backend types manuallyapi/selectors.ts$ for GraphQL variables from user input or props../api — never directly from ZeusqueryKeys from @/lib/queryKeys.js — never hardcode query key strings like ['me'] or ['todos']me query (enabled gating), but treat auth mutations as an exception and explicitly fetchQuery(queryKeys.me) to sync authenticated state deterministicallytools
Baseline architecture for Axolotl mobile starter (Expo Router + reusable blocks)
tools
Expo Router conventions for route groups, native headers, and starter navigation
development
i18n baseline and dev-translate setup for Expo mobile starter
development
Starter data layer pattern with React Query + Zeus for Expo app