Documentation
¶
Overview ¶
Package kvstate opts GWC state into transparent, durable key/value storage backed by the client-side SQLite driver (db/sqlite).
It mirrors ui.UsePersistedState (which uses localStorage) but routes through SQLite/IndexedDB, and every configurable axis is also a public interface a user can implement to build their own strategy:
- PersistenceBackend — where values live (default: a shared SQLite engine).
- WriteStrategy — when a dirty value becomes durable (Immediate, Debounced, OnUnload).
- Codec — how values are encoded (JSONCodec, CBORCodec).
- ConflictResolver — how concurrent/cross-tab writes are reconciled (LastWriteWins, Versioned).
Two ergonomic entry points share one engine:
- UsePersistedState[T] — a component-bound hook (async hydrate, write-through).
- BindAtom[T] — a global-atom adapter.
On native/SSR builds the underlying ui/interop stubs degrade gracefully; the SQLite engine itself still works (file-backed) so the durable logic is testable.
Index ¶
- func Import(parseCtx context.Context, parseBackend PersistenceBackend, ...) error
- func RegisterBackend(parseName string, parseBackend PersistenceBackend)
- func RegisterCodec(parseName string, parseCodec Codec)
- func RegisterResolver(parseName string, parseResolver ConflictResolver)
- func RegisterStrategy(parseName string, parseStrategy WriteStrategy)
- type BoundAtom
- type CBORCodec
- type Codec
- type ConflictResolver
- type Durability
- type HydrateMode
- type Immediate
- type JSONCodec
- type LastWriteWins
- type Options
- type PersistedState
- type PersistenceBackend
- type Record
- type Versioned
- type WriteStrategy
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Import ¶
func Import(parseCtx context.Context, parseBackend PersistenceBackend, parseRecords []Record, parseResolver ConflictResolver) error
Import writes records into a backend (ingress), resolving conflicts against any existing record with parseResolver. A nil resolver defaults to LastWriteWins. Records the resolver rejects (e.g. stale Versioned writes) are skipped, so an import never clobbers newer local state.
func RegisterBackend ¶
func RegisterBackend(parseName string, parseBackend PersistenceBackend)
RegisterBackend registers a PersistenceBackend under name.
func RegisterCodec ¶
RegisterCodec registers a Codec under name.
func RegisterResolver ¶
func RegisterResolver(parseName string, parseResolver ConflictResolver)
RegisterResolver registers a ConflictResolver under name.
func RegisterStrategy ¶
func RegisterStrategy(parseName string, parseStrategy WriteStrategy)
RegisterStrategy registers a WriteStrategy under name.
Types ¶
type BoundAtom ¶
type BoundAtom[T any] struct { // contains filtered or unexported fields }
BoundAtom is a global state.Atom whose value is durably persisted. Its Set/Update write through to the backend; reads come from the atom.
Atoms expose no global out-of-component subscription, so — like ui.UsePersistedState — persistence is captured through this returned handle.
func BindAtom ¶
func BindAtom[T any](parseCtx context.Context, parseAtom state.Atom[T], parseKey string, parseOptions ...Options) BoundAtom[T]
BindAtom binds a global atom to durable storage under parseKey. It loads the stored value asynchronously and applies it to the atom, then returns a write-through handle. Cross-tab writes are reflected back into the atom.
type CBORCodec ¶
type CBORCodec struct{}
CBORCodec encodes values as CBOR (compact binary). Uses the cbor dependency already vendored by the module.
type Codec ¶
type Codec interface {
Encode(parseValue any) ([]byte, error)
Decode(parseData []byte, parseTarget any) error
}
Codec encodes and decodes persisted values. Implement it to control the wire format (e.g. a compact or encrypted representation).
func CodecByName ¶
CodecByName returns a registered Codec.
type ConflictResolver ¶
ConflictResolver decides the winner when a local record meets an incoming one (e.g. a cross-tab write). Implement it for custom merge semantics.
func ResolverByName ¶
func ResolverByName(parseName string) (ConflictResolver, bool)
ResolverByName returns a registered ConflictResolver.
type Durability ¶
type Durability int
Durability selects the db/sqlite persistence backend. Its zero value (DurableDefault) means IndexedDB, so callers who want in-memory state must ask for it explicitly.
const ( // DurableDefault maps to IndexedDB. DurableDefault Durability = iota // DurableMemory keeps state in wasm memory only (lost on reload). DurableMemory // DurableIndexedDB snapshots to IndexedDB (the v1 durable backend). DurableIndexedDB // DurableOPFS is reserved; falls back to IndexedDB in v1. DurableOPFS )
type HydrateMode ¶
type HydrateMode int
HydrateMode controls when a value is loaded from the backend.
const ( // HydrateEager loads the stored value when the binding mounts. HydrateEager HydrateMode = iota // HydrateLazy does NOT auto-load the stored value: the binding starts at the supplied initial // value and the first Set persists forward over whatever was stored. Use it for write-through // state that should not read its prior value back on mount. // // NOTE: this is "skip the eager load", not "load on first read" — true load-on-first-read is not // implemented in the asynchronous-engine hook model (there is no synchronous read path to defer // into). Use HydrateEager when you need the stored value available after mount. HydrateLazy )
type Immediate ¶
type Immediate struct{}
Immediate flushes on every write. Simplest and safest; most IndexedDB traffic.
type LastWriteWins ¶
type LastWriteWins struct{}
LastWriteWins keeps the record with the newer UpdatedAt. It is the default.
func (LastWriteWins) Resolve ¶
func (LastWriteWins) Resolve(parseLocal, parseIncoming Record) Record
type Options ¶
type Options struct {
// Name is the logical database name (db/sqlite Options.Name). Default "gwc".
Name string
// Table is the KV table name. Default "gwc_state".
Table string
// Codec encodes/decodes values. Default JSONCodec.
Codec Codec
// Strategy decides when writes become durable. Default Debounced(150ms).
Strategy WriteStrategy
// Conflict reconciles concurrent/cross-tab writes. Default LastWriteWins.
Conflict ConflictResolver
// Hydrate controls load timing. Default HydrateEager.
Hydrate HydrateMode
// Durability selects the SQLite backend. Default DurableDefault (IndexedDB).
Durability Durability
// Backend overrides the persistence layer entirely. When nil, the shared
// SQLite engine is used.
Backend PersistenceBackend
}
Options configure a persisted binding. Any zero field takes its default, so the common case needs no Options at all.
type PersistedState ¶
type PersistedState[T any] struct { // contains filtered or unexported fields }
PersistedState is a state handle whose value is durably persisted via the configured backend. On native/SSR builds it degrades to plain in-memory state (the underlying ui.UseEffect is a no-op, so no engine is opened).
func UsePersistedState ¶
func UsePersistedState[T any](parseKey string, parseInitial T, parseOptions ...Options) PersistedState[T]
UsePersistedState returns state durably persisted under parseKey. It starts at parseInitial and asynchronously hydrates from the backend (the value is not available synchronously because the database opens asynchronously). Use Loading() to render a pending state if needed.
func (PersistedState[T]) Err ¶
func (parsePs PersistedState[T]) Err() error
Err returns the last persistence error, or nil.
func (PersistedState[T]) Get ¶
func (parsePs PersistedState[T]) Get() T
Get returns the current value.
func (PersistedState[T]) Loading ¶
func (parsePs PersistedState[T]) Loading() bool
Loading reports whether the initial hydrate from the backend is still pending.
func (PersistedState[T]) Set ¶
func (parsePs PersistedState[T]) Set(parseValue T)
Set updates the value in memory and persists it per the WriteStrategy.
type PersistenceBackend ¶
type PersistenceBackend interface {
Load(parseCtx context.Context, parseKey string) (parseRecord Record, parseFound bool, parseErr error)
Save(parseCtx context.Context, parseRecord Record) error
Delete(parseCtx context.Context, parseKey string) error
Keys(parseCtx context.Context) ([]string, error)
}
PersistenceBackend is the storage contract behind a binding. The default is the shared SQLite engine; implement this to persist elsewhere (a REST API, a custom OPFS layout, an in-memory mock) while keeping the same hook/atom API.
Cross-tab notification is handled separately (see watch.go), so a backend only needs to implement durable CRUD.
func BackendByName ¶
func BackendByName(parseName string) (PersistenceBackend, bool)
BackendByName returns a registered PersistenceBackend.
type Record ¶
type Record struct {
Key string
Value []byte
Version int64 // monotonic per key
UpdatedAt int64 // unix milliseconds
}
Record is one stored key/value with the metadata used for conflict resolution.
type Versioned ¶
type Versioned struct{}
Versioned keeps the record with the higher Version, rejecting stale writes.
type WriteStrategy ¶
type WriteStrategy interface {
OnWrite(parseCtx context.Context, parseKey string, parseFlush func(context.Context) error)
Close(parseCtx context.Context) error
}
WriteStrategy decides when a dirty value becomes durable. After each Set the binding calls OnWrite with a flush closure; the strategy chooses when (and how often) to invoke it. Close performs a final flush on teardown.
func Debounced ¶
func Debounced(parseD time.Duration) WriteStrategy
Debounced coalesces bursts of writes, flushing once after d of quiet. Good default for interactive state (typing, sliders).
func OnUnload ¶
func OnUnload() WriteStrategy
OnUnload defers flushing until teardown (Close) and, in the browser, also flushes on page hide. It minimizes writes for state that only needs to survive a reload, not every keystroke.
func StrategyByName ¶
func StrategyByName(parseName string) (WriteStrategy, bool)
StrategyByName returns a registered WriteStrategy.