query

package
v5.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package query is GoWebComponents' stable query/data layer: a keyed, concurrency-safe cache with request de-duplication, stale-while-revalidate (SWR) refresh, invalidate-by-key, and one-call optimistic mutations with automatic rollback.

It is the Go-native answer to TanStack Query / SWR, with two deliberate differences:

  • It is EXPLICIT, not magical. You pass the cache key and the fetcher to every call; nothing is auto-tracked behind your back. This matches the framework's core design direction (no hidden reactive graph) and keeps every call site readable.
  • It is PURE Go. The fetcher is an ordinary func() (T, error), so the entire layer compiles and is fully unit-testable both natively and as wasm — no JS, no network stubs, no fake timers needed (the clock is injectable for tests).

Typical use:

c := query.New(query.WithStaleTime(30 * time.Second))
res := query.Fetch(c, "user/42", func() (User, error) { return api.GetUser(42) })
// concurrent Fetch("user/42", …) calls coalesce into ONE api.GetUser call.

Index

Constants

View Source
const DefaultMaxEntries = 512

DefaultMaxEntries bounds a cache that does not set its own limit.

The map had no bound at all, and ensureEntry creates a record on every read path — including misses and fetches that error — so a parameterized key pattern retained one entry per distinct key forever. Search-as-you-type is the clearest case: one key per keystroke, none ever collected.

A COUNT bound rather than a time-to-live, deliberately. A default TTL would change behaviour for every existing caller, including apps whose working set is a handful of stable keys that are supposed to stay cached. A count only engages at a scale where unbounded growth is a bug rather than a working set, and the entry it drops is the least recently used — by definition the one nothing is looking at. Apps that want time-based collection can still call Evict / EvictPrefix on their own schedule.

Variables

This section is empty.

Functions

func MutateAsync

func MutateAsync[T any](parseC *Cache, parseKey string, parseOptimistic T, parseFn func() (T, error), parseOnSettled func(Result[T]))

MutateAsync applies the optimistic value to key immediately (so a Snapshot taken right after already reflects it) and runs fn in the background, committing fn's result on success or rolling back to the exact prior state on error, then invoking onSettled (may be nil) with the outcome. This is the fire-and-forget optimistic action: the UI updates now and the server reconciles later — pair fn with serverfn.Call to make a one-call optimistic server action.

Types

type Cache

type Cache struct {
	// contains filtered or unexported fields
}

Cache is a concurrency-safe keyed query cache. Create one per logical data scope (commonly one app-wide cache) and share it; all methods are safe for concurrent use.

func New

func New(parseOpts ...Option) *Cache

New creates an empty cache with the given options.

func (*Cache) Evict

func (parseC *Cache) Evict(parseKey string)

Evict removes key from the cache entirely, releasing its retained value. Unlike Invalidate (which keeps the value and marks it stale), Evict is for keys that will not be read again — pagination cursors, per-item detail keys, search strings — so a long-lived session does not grow the cache without bound. An in-flight fetch for the key completes harmlessly: its result is dropped (the entry is gone) and joined callers wake normally.

func (*Cache) EvictAll

func (parseC *Cache) EvictAll()

EvictAll removes every cached entry, releasing all retained values.

func (*Cache) EvictPrefix

func (parseC *Cache) EvictPrefix(parsePrefix string)

EvictPrefix removes every key sharing prefix — the by-scope sibling of Evict.

func (*Cache) Inspect

func (parseC *Cache) Inspect() []EntryInfo

Inspect returns a sorted, type-erased view of every cache entry — the data a devtools cache panel renders (which keys are cached, fresh/stale, currently fetching).

func (*Cache) Invalidate

func (parseC *Cache) Invalidate(parseKey string)

Invalidate marks key stale so the next Fetch/SWR refetches it. In-flight fetches are left untouched.

func (*Cache) InvalidateAll

func (parseC *Cache) InvalidateAll()

InvalidateAll marks every cached key stale so the next read of each refetches. This is the global cache-bust used on sign-in/sign-out or a window-focus/reconnect revalidation.

func (*Cache) InvalidatePrefix

func (parseC *Cache) InvalidatePrefix(parsePrefix string)

InvalidatePrefix marks every key sharing prefix stale — the by-scope invalidation that makes keys like "user/42/posts" cheap to refresh as a group.

func (*Cache) Keys

func (parseC *Cache) Keys() []string

Keys returns a sorted snapshot of the cached keys — for devtools, introspection, and deciding which queries a focus/reconnect handler should revalidate.

func (*Cache) Peek

func (parseC *Cache) Peek(parseKey string) (any, bool)

Peek returns the cached value for key without triggering a fetch.

func (*Cache) Set

func (parseC *Cache) Set(parseKey string, parseData any)

Set seeds or overwrites the cached value for key (e.g. priming from SSR data or a mutation response), marking it freshly updated.

type EntryInfo

type EntryInfo struct {
	Key       string
	HasData   bool
	Stale     bool
	Fetching  bool
	UpdatedAt time.Time
}

EntryInfo is a non-generic, type-erased view of one cache entry for observability — the devtools surface that does not know each key's concrete type.

type Option

type Option func(*Cache)

Option configures a Cache at construction.

func WithClock

func WithClock(parseNow func() time.Time) Option

WithClock injects the clock used for freshness decisions (defaults to time.Now). Supply a deterministic clock to make staleness testable without sleeping, or a server-synchronized clock so cache TTLs agree with an authoritative time source.

func WithMaxEntries

func WithMaxEntries(parseMax int) Option

WithMaxEntries bounds how many keys the cache retains, evicting the least-recently-used entry when a new key would exceed the limit. The default is DefaultMaxEntries. Zero or negative disables the bound entirely, which restores the previous unbounded behaviour — appropriate only when the key space is known to be small and fixed.

func WithStaleTime

func WithStaleTime(parseD time.Duration) Option

WithStaleTime sets how long fetched data is considered fresh. Within this window Fetch returns cached data without calling the fetcher and SWR skips revalidation. The default is 0: every read revalidates (concurrent reads still coalesce).

type Result

type Result[T any] struct {
	// Data is the cached value (zero if none has been fetched successfully).
	Data T
	// Err is the most recent error for this key, or nil.
	Err error
	// Status is the lifecycle state at snapshot time.
	Status Status
	// UpdatedAt is when Data was last written, zero if never.
	UpdatedAt time.Time
	// Stale is true when Data is older than the cache's stale time (SWR will refresh).
	Stale bool
	// Fetching is true when a background or foreground fetch is in flight.
	Fetching bool
}

Result is an immutable snapshot of a cached query at one moment.

func Fetch

func Fetch[T any](parseC *Cache, parseKey string, parseFn func() (T, error)) Result[T]

Fetch returns the cached value for key when fresh; otherwise it invokes fn — coalescing all concurrent Fetch calls for the same key into a single fn invocation — caches the result, and returns it. This is the blocking, read-through path.

func Mutate

func Mutate[T any](parseC *Cache, parseKey string, parseOptimistic T, parseFn func() (T, error)) Result[T]

Mutate applies an optimistic value to key immediately, runs fn, and on success commits fn's returned value to the cache — or, on error, rolls the cache back to exactly the pre-mutation state and reports the error in the returned Result. This is the optimistic-update-with-rollback path that JS query libs make you wire by hand.

func SWR

func SWR[T any](parseC *Cache, parseKey string, parseFn func() (T, error), parseOnUpdate func(Result[T])) Result[T]

SWR returns the cached value immediately (possibly empty or stale) and, when the data is missing or stale and no fetch is already running, starts a background refresh. onUpdate (may be nil) is called once with the fresh result when that refresh completes, which is where a component re-renders. Concurrent SWR/Fetch calls share one refresh.

func Snapshot

func Snapshot[T any](parseC *Cache, parseKey string) Result[T]

Snapshot returns the current typed result for key WITHOUT triggering a fetch. It is the pure read a render uses to project current cache state into the view; pair it with SWR (or the ui.UseQuery hook) to drive the actual revalidation.

type Status

type Status int

Status is the lifecycle state of a cached query result.

const (
	// StatusIdle means the key has never been fetched and holds no data.
	StatusIdle Status = iota
	// StatusLoading means a fetch is in flight and no data is cached yet.
	StatusLoading
	// StatusSuccess means data is cached (it may be stale and revalidating).
	StatusSuccess
	// StatusError means the last fetch failed and no prior data is cached.
	StatusError
)

func (Status) String

func (parseS Status) String() string

String renders the status for logs and diagnostics.

Directories

Path Synopsis
Package devtools renders the query cache's observability panel as a GoWebComponents component (D2).
Package devtools renders the query cache's observability panel as a GoWebComponents component (D2).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL