data — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited data (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Backend-agnostic. React Query owns all server data. Never fetch in useEffect.
src/libs/query-client.ts)import { MutationCache, QueryCache, QueryClient } from '@tanstack/react-query'
import { toast } from 'sonner-native'
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
gcTime: Infinity,
retry: false,
refetchOnMount: false,
refetchOnWindowFocus: false,
},
},
queryCache: new QueryCache({
onError: (error) => toast.error(error.message),
}),
mutationCache: new MutationCache({
onError: (error) => toast.error(error.message),
}),
})src/libs/axios.ts)import axios from 'axios'
import * as SecureStore from 'expo-secure-store'
export const api = axios.create({
baseURL: process.env.EXPO_PUBLIC_API_URL,
timeout: 30_000,
headers: { 'Content-Type': 'application/json' },
})
// Attach auth token
api.interceptors.request.use(async (config) => {
const token = await SecureStore.getItemAsync('access_token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
// Refresh token on 401
api.interceptors.response.use(
(res) => res,
async (error) => {
if (error.response?.status === 401) {
// refresh logic here
}
return Promise.reject(error)
},
)4 files per domain in src/api/<domain>/:
src/api/posts/
├── types.ts ← Zod schemas + inferred types
├── api.ts ← axios calls (pure functions)
├── keys.ts ← query key factory
└── hooks.ts ← useQuery/useMutation wrapperstypes.tsimport { z } from 'zod'
export const postSchema = z.object({
id: z.string(),
title: z.string(),
body: z.string(),
createdAt: z.string(),
})
export type Post = z.infer<typeof postSchema>
export const createPostSchema = z.object({
title: z.string().min(1),
body: z.string().min(1),
})
export type CreatePostInput = z.infer<typeof createPostSchema>api.tsimport { api } from '@/libs/axios'
import type { Post, CreatePostInput } from './types'
export async function fetchPosts(): Promise<Post[]> {
const { data } = await api.get('/posts')
return data
}
export async function fetchPost(id: string): Promise<Post> {
const { data } = await api.get(`/posts/${id}`)
return data
}
export async function createPost(input: CreatePostInput): Promise<Post> {
const { data } = await api.post('/posts', input)
return data
}
export async function deletePost(id: string): Promise<void> {
await api.delete(`/posts/${id}`)
}keys.tsexport const postKeys = {
all: ['posts'] as const,
lists: () => [...postKeys.all, 'list'] as const,
list: (params?: Record<string, unknown>) => [...postKeys.lists(), params] as const,
details: () => [...postKeys.all, 'detail'] as const,
detail: (id: string) => [...postKeys.details(), id] as const,
}hooks.tsimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { createPost, deletePost, fetchPost, fetchPosts } from './api'
import { postKeys } from './keys'
export function usePosts() {
return useQuery({ queryKey: postKeys.list(), queryFn: fetchPosts })
}
export function usePost(id: string) {
return useQuery({ queryKey: postKeys.detail(id), queryFn: () => fetchPost(id) })
}
export function useCreatePost() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: createPost,
onSuccess: () => queryClient.invalidateQueries({ queryKey: postKeys.lists() }),
})
}
export function useDeletePost() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: deletePost,
onSuccess: () => queryClient.invalidateQueries({ queryKey: postKeys.lists() }),
})
}function PostsScreen() {
const { data: posts, isPending, isError } = usePosts()
const { mutate: createPost, isPending: isCreating } = useCreatePost()
if (isPending) return <ActivityIndicator />
if (isError) return <Text>Something went wrong</Text>
return (
<FlashList
data={posts}
renderItem={({ item }) => <PostCard id={item.id} title={item.title} />}
estimatedItemSize={80}
/>
)
}// hooks.ts
export function useInfinitePosts() {
return useInfiniteQuery({
queryKey: postKeys.list({ infinite: true }),
queryFn: ({ pageParam }) => fetchPostsPage({ cursor: pageParam }),
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
}export function useDeletePost() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: deletePost,
onMutate: async (id) => {
await queryClient.cancelQueries({ queryKey: postKeys.lists() })
const previous = queryClient.getQueryData(postKeys.list())
queryClient.setQueryData(postKeys.list(), (old: Post[]) =>
old.filter((p) => p.id !== id),
)
return { previous }
},
onError: (_, __, ctx) => {
queryClient.setQueryData(postKeys.list(), ctx?.previous)
},
onSettled: () => queryClient.invalidateQueries({ queryKey: postKeys.lists() }),
})
}useEffect -- always useQueryinvalidateQueries in onSettled (not onSuccess) to handle error casesonMutate for optimistic updatesstaleTime: Infinity -- data never goes stale automatically, refetch manually~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.