@leather.io/queries
v0.3.1
Published
Canonical React Query configurations for Leather services
Readme
@leather.io/queries
Canonical React Query configurations for Leather's services layer.
Purpose
This package provides standardized query key factories and configuration builders for @leather.io/services, ensuring consistent caching behavior and dependency management across all Leather applications.
- Eliminates opaque dependencies – Query keys automatically include the settings required by each service
- Single source of truth – One canonical way to call each service from React Query
- Type-safe – Full TypeScript support with service return types
- Maintainable – Add new configs by extending a small registry + factory
Architecture
Apps (extension/mobile/web)
↓
@leather.io/queries
↓
@leather.io/services (business logic)This package serves as the adapter layer between React Query and the services layer, keeping services framework-agnostic while providing idiomatic React Query integration.
Usage
Basic Pattern
import { useQuery } from '@tanstack/react-query';
import { createMarketDataQueryConfig } from '@leather.io/queries';
function useMarketData(asset: FungibleCryptoAsset) {
const settings = useUserSettings(); // App-specific adapter -> UserSettings
return useQuery(createMarketDataQueryConfig(asset, settings));
}Available Query Configs
- Market data –
createMarketDataQueryKey,createMarketDataQueryConfig - BTC balances –
createBtcBalanceQueryKey,createBtcBalanceQueryConfig, aggregate helpers - SIP-10 balances –
createSip10AccountBalanceQueryKey,createSip10AccountBalanceQueryConfig,createSip10AddressBalanceQueryKey,createSip10AddressBalanceQueryConfig - Activity –
createActivityQueryKey,createActivityQueryConfig,createActivityByAssetQueryKey,createActivityByAssetQueryConfig - SIP-10 activity –
createSip10ActivityByAssetIdQueryKey,createSip10ActivityByAssetIdQueryConfig
Each helper accepts:
- The service params (asset, account request, etc.)
- A
UserSettingsobject supplied by the consuming app
How It Works
Query Key Registry
src/shared/query-key.registry.ts is the single source of truth for which settings dependencies a given service call cares about. Each entry looks like this:
export const querySettingsDepsRegistry = {
'market-data-service--get-market-data': ['currency', 'network'],
'sip10-balances-service--get-sip10-account-balance': ['currency', 'network', 'assetVisibility'],
} as const;The createServiceQueryKey() factory uses this registry to pull the required values (via the UserSettings adapter), ensuring query keys react to changes in currency, network, or asset visibility only when necessary.
Query Option Presets
balanceQueryOptions– Tight 5s cache window for balance readsmarketDataQueryOptions– 1 minute cache window for price data
Adding New Query Configs
Follow this pattern when adding new service query configs:
- Add registry entry
- In
src/shared/query-key.registry.ts, register a new prefix + required deps.
- In
- Create config file
- e.g.
src/activity/activity.query-config.ts
- e.g.
- Key factory
- Use
createServiceQueryKey(prefix, paramsArray, settings)
- Use
- Config builder
- Return a
UseQueryOptionsthat spreads the shared query options and calls the service
- Return a
- Export
- Re-export from
index.ts
- Re-export from
Lint Invariants
To keep this package and its consumers aligned on the “builder only” pattern, a few ESLint rules are enforced:
- No React Query hooks in
@leather.io/queriespackages/queriesmay import types from@tanstack/react-query(e.g.UseQueryOptions,QueryFunctionContext), but importing hooks likeuseQueryoruseInfiniteQueryis disallowed.- This guarantees the package only exports pure key/config builders and never React hooks.
- No
use*exports in@leather.io/queries- Declarations whose identifiers start with
use[A-Z]are rejected in this package. - Hooks are defined in apps (
apps/web,apps/mobile,apps/extension), not in the query-config layer.
- Declarations whose identifiers start with
- No
*QueryOptionswrapper types in@leather.io/queries- Type aliases ending in
QueryOptionsare banned here to avoid patterns likeOmit<UseQueryOptions, 'queryKey' | 'queryFn'>. - Apps should use
UseQueryOptions(orPartial<UseQueryOptions<...>>) directly when they need configuration objects.
- Type aliases ending in
- App query modules must not import certain
get*Servicehelpers- In
apps/*query directories we forbid importinggetActivityService,getMarketDataService, andgetBtcBalancesServicefrom@leather.io/services. - Instead, query modules should rely on the corresponding builders exported from
@leather.io/queriesand calluseQuerythemselves.
- In
useQueryobject calls must be complete or builder-based- For object-literal
useQuery({ ... })calls in app query modules, ESLint requires either:- the object spreads an existing query config (e.g.
...createXQueryConfig(...)), or - it explicitly defines both
queryKeyandqueryFn.
- the object spreads an existing query config (e.g.
- This helps prevent accidental “wrapper” helpers that partially hide React Query configuration or forget to specify a key or fetch function.
- For object-literal
Design Principles
- Export builders, not hooks – Apps own their React Query hooks
- Always accept
UserSettings– Drive reactivity through the adapter pattern - Use the registry – Every query key goes through
createServiceQueryKey - Keep it pure – Builders are deterministic and side-effect free
Relationship to Other Packages
@leather.io/services- This package wraps service calls with query configs@leather.io/query(legacy) - Older package with low-level API queries, being gradually deprecated@tanstack/react-query- Peer dependency, types imported but not included in runtime
Migration from Inline Queries
Before (manual query key management):
function useBtcBalance(request: AccountRequest) {
const { fiatCurrencyPreference } = useSettings(); // Easy to forget!
return useQuery({
queryKey: ['btc-balance', request, fiatCurrencyPreference], // Manual key construction
queryFn: ({ signal }) => getBtcBalancesService().getBtcAccountBalance(request, signal),
staleTime: 5000,
// ... more options
});
}After (using query config):
function useBtcBalance(request: AccountRequest) {
const settings = useSettings();
return useQuery(createBtcBalanceQueryConfig(request, settings));
}Benefits:
- ✅ Dependencies are never forgotten (enforced by config builder)
- ✅ Query keys are consistent across the app
- ✅ Query options are standardized
- ✅ Less boilerplate in application code
License
MIT
