Askr
Routing & Data

Queries and Consistency

Use queries and consistency with the current published Askr packages.

  • @askrjs/askr/data0.0.59

How to use queries and consistency

Start with this complete queries and consistency shape, then replace the example data and application service with your own.

  1. Import the published @askrjs/askr/data entrypoint.
  2. Keep queries and consistency configuration next to the component, route, or server composition root that owns it.
  3. Handle pending, unavailable, cancellation, and failure states, then verify the production path.
const project = defineQuery({
  key: ({ id }: { id: string }) => 'project:' + id,
  fetch: ({ id, signal }) => api.projects.get(id, { signal }),
});

const result = createQuery(project, { id: projectId });

Define a query

defineQuery() takes a QueryDefinition — a key(input) function that derives a cache key, and a fetch(context) function that does the actual request — and hands back a reusable definition you can pass to createQuery(), serveQuery(), or prefetchQuery(). createQuery() also accepts a plain QueryOptions object directly if you don't need the input/key(input) split. Optional isConsistent() and reconcile() functions on the definition let you decide whether freshly fetched data actually replaces what's cached.

Consistency modes

Instead of a single isLoading flag, a Query<T>'s consistency field is one of 'fresh', 'stale', 'refreshing', or 'pending-write', and each value comes with a matching shape for data, error, and stale flags — a refreshing query still has its previous data available, for instance, while a stale query with an error has both data: null and staleReason: 'error'. Reading consistency first tells you which of those shapes you're dealing with before you touch data or error directly.

Query scopes

queryScope(namespace) returns a QueryScope with key() and prefix() helpers that build namespaced cache keys, plus its own invalidate(parts, options) method scoped to that namespace. This is useful once query keys get structured — a scope per resource type keeps key-building consistent and keeps invalidation calls from accidentally matching keys that belong to a different feature.

Refresh behavior

Every Query<T> carries a refresh() method from QueryControls that re-runs the fetch and returns a promise, independent of whatever caused the original fetch. Whether the refreshed data actually replaces the cached value depends on the definition's reconcile() function, if one is supplied — it gets the new data plus the key and can accept or reject it, which is how a query avoids clobbering good data with a response that arrived out of order.