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
- func MutateAsync[T any](parseC *Cache, parseKey string, parseOptimistic T, parseFn func() (T, error), ...)
- type Cache
- func (parseC *Cache) Evict(parseKey string)
- func (parseC *Cache) EvictAll()
- func (parseC *Cache) EvictPrefix(parsePrefix string)
- func (parseC *Cache) Inspect() []EntryInfo
- func (parseC *Cache) Invalidate(parseKey string)
- func (parseC *Cache) InvalidateAll()
- func (parseC *Cache) InvalidatePrefix(parsePrefix string)
- func (parseC *Cache) Keys() []string
- func (parseC *Cache) Peek(parseKey string) (any, bool)
- func (parseC *Cache) Set(parseKey string, parseData any)
- type EntryInfo
- type Option
- type Result
- func Fetch[T any](parseC *Cache, parseKey string, parseFn func() (T, error)) Result[T]
- func Mutate[T any](parseC *Cache, parseKey string, parseOptimistic T, parseFn func() (T, error)) Result[T]
- func SWR[T any](parseC *Cache, parseKey string, parseFn func() (T, error), ...) Result[T]
- func Snapshot[T any](parseC *Cache, parseKey string) Result[T]
- type Status
Constants ¶
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 (*Cache) Evict ¶
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 ¶
EvictPrefix removes every key sharing prefix — the by-scope sibling of Evict.
func (*Cache) Inspect ¶
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 ¶
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 ¶
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 ¶
Keys returns a sorted snapshot of the cached keys — for devtools, introspection, and deciding which queries a focus/reconnect handler should revalidate.
type EntryInfo ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
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 )