Primitives

Navigation

Separate selected intent from committed content, with bounded prefetch.

On this page

initial is { destination: string, data: T }. load(destination, context) returns the destination's data. The store adds:

  • selected: requested destination while pending; otherwise the committed destination.
  • current: the destination whose data has committed.
  • data: the most recently committed { destination, data } pair; always defined, including in the TypeScript type.

Methods are navigate, retry, prefetch, invalidatePrefetch, cancel, reset, and dispose. Navigation always uses latest-intent semantics. Failure and cancellation restore selected to current; failed destination information remains in input for recovery. Navigating to the current destination loads it again, consuming a matching prefetch if available.

This is a content-navigation primitive, not a URL router. An adapter must own browser history, back/forward navigation, scroll, focus, and redirects. See integrations.

Prefetch options and results

Prefetch is disabled by default. Set prefetch: {} to opt in. Requests begin only when you call prefetch(destination).

Option Default Meaning
ttlMs 10_000 Lifetime from successful completion. Repeated access does not extend it.
maxEntries 8 Maximum speculative entries, including in-flight work. Completed entries use least-recently-used eviction.
maxConcurrent 2 Maximum simultaneous speculative requests. Excess calls are skipped, not queued.
timeoutMs 5_000 Speculative timeout. Removed when navigation claims the request.

Limits must be positive safe integers; timer durations follow the same constraints as task timers. A zero TTL disables retention after completion while preserving in-flight deduplication. Expired entries are pruned on access; settled entries do not keep background timers alive.

ts
type PrefetchResult<T> =
  | { status: 'success'; data: T }
  | { status: 'error'; error: unknown }
  | {
      status: 'skipped';
      reason: 'disabled' | 'capacity' | 'concurrency' | 'active';
    }
  | { status: 'cancelled' | 'disposed' };

Repeated prefetch calls for an existing destination share the exact same promise. Prefetch never changes the navigation snapshot or surfaces an error there. Failed entries are removed so a later navigation can fetch fresh data. The active skip reason means that destination is already being loaded by a foreground navigation.

Navigation consumes a matching entry once. If it is still pending, cancellation ownership transfers to that navigation, its speculative timeout is cleared, and the navigation's own optional timeout starts. Superseding, cancelling, or disposing that navigation requests abort of the original transport. An actual navigation is never blocked by a speculative request budget; foreground requests are outside that budget. Foreground successes do not populate the speculative buffer.

invalidatePrefetch(destination?) removes one exact key or all speculative entries and requests abort of their pending work. It does not affect a request already claimed by navigation. reset() clears both speculative entries and navigation state; dispose() settles and cleans up all owned work. Speculative loader contexts have negative id values; foreground task ids are positive. A promoted request keeps its original context and signal.

Keys are exact strings. Invalidate after writes or user-context changes; include relevant filters and identity in keys. Operation callback changes do not invalidate already warmed data. See prefetching and debounce for integration guidance.

View this page on GitHub ↗