Skip to content
effect-api-query

Defaults and Policies

Reuse native cache defaults and typed query or mutation policies.

Choose where to define each setting before reusing it across generated builders:

Scope Use Suitable settings
Cache owner or namespace QueryClient defaults and generated prefixes Common freshness, retry, garbage collection, and hashing
One leaf and query view withQueryPolicy Freshness and typed data and error callbacks
One mutation leaf withMutationPolicy Typed invalidation and other mutation callbacks
One call The options builder Input, selection, initial data, pagination, and local overrides

Use your existing utility tree and application-owned QueryClient. Policies preserve the original builder’s inference. The library supplies the generated keys and execution functions.

In the cache module, import QueryClient from @tanstack/query-core and viewPrefix from @ueberbrot/effect-api-query:

const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 30_000, gcTime: 600_000, retry: false, retryDelay: 250 },
mutations: { retry: false },
},
})

After creating rpc with its ready client, register broader prefixes before narrower prefixes:

queryClient.setQueryDefaults(rpc.key(), { staleTime: 60_000 })
queryClient.setQueryDefaults(rpc.users.key(), { retry: 2 })
queryClient.setQueryDefaults(viewPrefix(rpc.users.get, 'query'), { staleTime: 120_000 })

TanStack merges global defaults, matching registered prefixes in registration order, and explicit builder options. It does not sort prefixes by specificity. Here, users.get inherits gcTime: 600_000, retry: 2, and staleTime: 120_000. Two retries allow at most three executions.

rpc.key() covers the tree, rpc.users.key() covers its branch, and the view prefix covers only ordinary queries of that leaf, for every input. HTTP uses the same native APIs:

queryClient.setQueryDefaults(http.users.key(), { retry: 1 })
queryClient.setQueryDefaults(viewPrefix(http.users.get, 'metadata'), { staleTime: 5_000 })

This five-second policy applies to metadata without changing the ordinary view’s freshness. Register mutation defaults separately with native setMutationDefaults and mutationKey(). See Cache Keys and Filters for prefix scopes.

Before using the cache, configure hashing through QueryClient defaults. Keep hashing stable for the cache owner’s lifetime. Builders reject per-call hashing overrides. See Semantic Keys for the identity contract.

If a policy belongs to one leaf and view, use withQueryPolicy. QueryPolicy types reusable objects with that leaf’s native options. Retry and refetch callbacks retain its data and error types. Import both from @ueberbrot/effect-api-query:

const policy = {
staleTime: 30_000,
refetchOnWindowFocus: false,
retry: false,
} satisfies QueryPolicy<typeof rpc.users.get>
const userOptions = withQueryPolicy(rpc.users.get, 'query', policy)
const user = userOptions({ input: { id: 1 }, select: (value) => value.name })
const refreshUser = userOptions({ input: { id: 1 }, staleTime: 0, retry: false })

The returned builder keeps selected-data, defined-initial-data, skipped-input, and page-parameter inference. QueryPolicy<Leaf, View> supports only that leaf’s query views: query, infinite, metadata, live, or streamed. Specify the view in the type when it is not query. See Type Inference for cache and failure types, and Retry Queries for reusable domain-aware decisions.

Policies accept applicable native freshness, retry, refetch, and cache settings. Keep input, select, initial and placeholder data, page parameters and mappers, and adapter controls at the call site. Stream retention and decoder policies determine cache identity and also stay local.

A bound policy supplies explicit builder options, so it overrides QueryClient defaults. Call-site options override the bound policy. To inherit a field, omit it. To replace an inherited setting, use a value such as staleTime: 0 or retry: false.

Import withMutationPolicy from @ueberbrot/effect-api-query. Seed the returned user and invalidate affected reads once for every use of the creation builder:

const createUserOptions = withMutationPolicy(rpc.users.create, {
onSuccess: async (created) => {
queryClient.setQueryData(rpc.users.get.queryKey({ id: created.id }), created)
await queryClient.invalidateQueries({ queryKey: rpc.users.key() })
},
})

Shared callbacks infer data, variables, and execution errors. MutationPolicy<Leaf> can annotate a reusable object. Its optimistic result is unknown. To infer that result in local callbacks, supply onMutate at each call site. Here, reportCreationFailure notifies the application of a failure:

const createUser = createUserOptions({
onMutate: (variables) => ({ submittedName: variables.name }),
onError: (error, _variables, context) => {
reportCreationFailure(context?.submittedName, error)
},
})

A call-site callback replaces the corresponding shared callback. Compose callbacks explicitly when both must run. The complete example includes ready-client types and the imports used here.