Documentation
¶
Overview ¶
Package state provides batch update support for reactive primitives. Batching is request-scoped: notifications are deferred until the batch completes, then all subscribers are notified at once. This prevents intermediate state updates from triggering multiple re-renders or WebSocket messages.
Package state provides Derived[T] for computed/derived reactive state. Derived values automatically recalculate when their dependencies change.
Package state provides Effect for reactive side effects. Effects run when their dependencies change, similar to Svelte's $effect rune.
Package state provides build-time state pruning for GoSPA applications. This reduces bundle size by eliminating unused state at build time.
Package state provides Svelte rune-like reactive primitives for Go. Rune[T] is the base reactive primitive that holds a value and notifies subscribers on changes.
Package state provides serialization utilities for reactive state. These helpers convert state to JSON for client transmission.
Index ¶
- func Batch(fn func())
- func BatchError(fn func() error) error
- func BatchResult[T any](fn func() T) T
- func BatchWithContext(ctx context.Context, fn func() error) error
- func CalculateTreeSize(tree *StateTree) int
- func FlushPendingNotifications(ctx context.Context)
- func IsInBatch() bool
- func SerializePrunedState(state map[string]any, usedPaths map[string]bool) map[string]any
- func SerializeState(runes map[string]interface{}) ([]byte, error)
- type CleanupFunc
- type Derived
- func Derived2[A, B, T any](a *Rune[A], b *Rune[B], combine func(A, B) T) *Derived[T]
- func Derived3[A, B, C, T any](a *Rune[A], b *Rune[B], c *Rune[C], combine func(A, B, C) T) *Derived[T]
- func DerivedFrom[T any](compute func() T, observables ...Observable) *Derived[T]
- func NewDerived[T any](compute func() T) *Derived[T]
- func (d *Derived[T]) DependOn(o Observable)
- func (d *Derived[T]) Dispose()
- func (d *Derived[T]) Get() T
- func (d *Derived[T]) GetAny() any
- func (d *Derived[T]) ID() string
- func (d *Derived[T]) MarshalJSON() ([]byte, error)
- func (d *Derived[T]) Subscribe(fn Subscriber[T]) Unsubscribe
- func (d *Derived[T]) SubscribeAny(fn func(any)) Unsubscribe
- type Effect
- type EffectFn
- type Observable
- type PruningConfig
- type PruningReport
- type Rune
- func (r *Rune[T]) Get() T
- func (r *Rune[T]) GetAny() any
- func (r *Rune[T]) ID() string
- func (r *Rune[T]) MarshalJSON() ([]byte, error)
- func (r *Rune[T]) Set(value T)
- func (r *Rune[T]) SetAny(value any) error
- func (r *Rune[T]) Subscribe(fn Subscriber[T]) Unsubscribe
- func (r *Rune[T]) SubscribeAny(fn func(any)) Unsubscribe
- func (r *Rune[T]) Update(fn func(T) T)
- type Serializable
- type Settable
- type StateDiff
- type StateMap
- func (sm *StateMap) Add(name string, obs Observable) *StateMap
- func (sm *StateMap) AddAny(name string, value interface{}) *StateMap
- func (sm *StateMap) ForEach(fn func(key string, value any))
- func (sm *StateMap) Get(name string) (Observable, bool)
- func (sm *StateMap) MarshalJSON() ([]byte, error)
- func (sm *StateMap) ToJSON() (string, error)
- func (sm *StateMap) ToMap() map[string]any
- type StateMessage
- func NewErrorMessage(componentID, errMsg string) *StateMessage
- func NewInitMessage(componentID string, state interface{}) *StateMessage
- func NewSyncMessage(componentID string, state interface{}) *StateMessage
- func NewUpdateMessage(componentID, key string, value interface{}) *StateMessage
- func ParseMessage(data []byte) (*StateMessage, error)
- type StatePruner
- type StateSnapshot
- type StateTree
- type StateUsage
- type StateValidator
- type Subscriber
- type Unsubscribe
- type Validator
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Batch ¶
func Batch(fn func())
Batch executes the given function with notification batching enabled. All state changes within the function will have their subscriber notifications deferred until the batch completes, at which point all subscribers are notified at once. This prevents cascading updates and reduces re-renders.
Example:
Batch(func() {
count.Set(10)
name.Set("Alice") // Subscribers notified after both updates complete
})
func BatchError ¶
BatchError executes the given function with batching and returns any error. All state changes within the function will be batched and notifications deferred until the batch completes, regardless of error status.
func BatchResult ¶
func BatchResult[T any](fn func() T) T
BatchResult executes the given function with batching and returns its result. All notifications are deferred until the function completes.
func BatchWithContext ¶ added in v0.1.5
BatchWithContext executes the given function with notification batching using the provided context. This allows for request-scoped batching in HTTP handlers. The context must support value storage.
Example in a Fiber handler:
app.Get("/api/update", func(c *fiber.Ctx) error {
return state.BatchWithContext(c.Context(), func() error {
counter.Set(counter.Get() + 1)
lastUpdated.Set(time.Now())
return nil
})
})
func CalculateTreeSize ¶
CalculateTreeSize calculates the estimated size of a state tree.
func FlushPendingNotifications ¶ added in v0.1.5
FlushPendingNotifications immediately sends all pending notifications for the given context. This is useful when you need to ensure updates are sent before a long-running operation or before returning from a handler.
func IsInBatch ¶ added in v0.1.5
func IsInBatch() bool
IsInBatch returns true if there is an active batch operation
func SerializePrunedState ¶
SerializePrunedState serializes only the used portions of state.
func SerializeState ¶
SerializeState serializes multiple runes into a JSON object
Types ¶
type Derived ¶
type Derived[T any] struct { // contains filtered or unexported fields }
Derived is a computed value that automatically updates when dependencies change. Similar to Svelte's $derived rune, it recalculates its value based on a compute function and notifies subscribers when the computed value changes.
func Derived3 ¶
func Derived3[A, B, C, T any](a *Rune[A], b *Rune[B], c *Rune[C], combine func(A, B, C) T) *Derived[T]
Derived3 creates a derived value from three runes with a combine function.
func DerivedFrom ¶
func DerivedFrom[T any](compute func() T, observables ...Observable) *Derived[T]
DerivedFrom creates a derived value that depends on one or more observables. This is a convenience function that automatically sets up dependencies.
Example:
count := state.NewRune(5)
doubled := state.DerivedFrom(func() int {
return count.Get() * 2
}, count)
func NewDerived ¶
NewDerived creates a new derived value from a compute function. The compute function is called immediately to get the initial value.
Example:
count := state.NewRune(5)
doubled := state.NewDerived(func() int {
return count.Get() * 2
})
func (*Derived[T]) DependOn ¶
func (d *Derived[T]) DependOn(o Observable)
DependOn adds an observable as a dependency of this derived value. When the observable changes, this derived value will be marked dirty.
Example:
count := state.NewRune(5)
doubled := state.NewDerived(func() int {
return count.Get() * 2
})
doubled.DependOn(count)
func (*Derived[T]) Dispose ¶
func (d *Derived[T]) Dispose()
Dispose cleans up all subscriptions to dependencies
func (*Derived[T]) Get ¶
func (d *Derived[T]) Get() T
Get returns the current computed value. If dependencies have changed, it recomputes first.
func (*Derived[T]) GetAny ¶
GetAny returns the current value of the derivative as an interface{}. This implements the Observable interface.
func (*Derived[T]) MarshalJSON ¶
MarshalJSON implements json.Marshaler for serialization
func (*Derived[T]) Subscribe ¶
func (d *Derived[T]) Subscribe(fn Subscriber[T]) Unsubscribe
Subscribe registers a callback for when the derived value changes. Returns an unsubscribe function.
func (*Derived[T]) SubscribeAny ¶
func (d *Derived[T]) SubscribeAny(fn func(any)) Unsubscribe
SubscribeAny registers a type-erased callback. This implements the Observable interface.
type Effect ¶
type Effect struct {
// contains filtered or unexported fields
}
Effect represents a reactive side effect that runs when dependencies change.
func EffectOn ¶
func EffectOn(fn EffectFn, observables ...Observable) *Effect
EffectOn creates an effect that depends on one or more observables. This is a convenience function that automatically sets up dependencies.
Example:
count := state.NewRune(0)
effect := state.EffectOn(func() state.CleanupFunc {
fmt.Println("Count is:", count.Get())
return nil
}, count)
defer effect.Dispose()
func NewEffect ¶
NewEffect creates a new effect that runs the given function. The function can return a cleanup function that runs before the next execution or when the effect is disposed.
Example:
count := state.NewRune(0)
effect := state.NewEffect(func() state.CleanupFunc {
fmt.Println("Count is:", count.Get())
return func() {
fmt.Println("Cleaning up")
}
})
defer effect.Dispose()
func (*Effect) DependOn ¶
func (e *Effect) DependOn(o Observable)
DependOn adds an observable as a dependency of this effect. When the observable changes, the effect will re-run.
func (*Effect) Dispose ¶
func (e *Effect) Dispose()
Dispose permanently stops the effect and cleans up resources
type Observable ¶
type Observable interface {
SubscribeAny(func(any)) Unsubscribe
GetAny() any
}
Observable provides a type-erased interface for state primitives. This allows storing mixed-type Runes in a single collection.
type PruningConfig ¶
type PruningConfig struct {
// RootDir is the project root directory.
RootDir string `json:"rootDir"`
// OutputDir is where pruned state files are written.
OutputDir string `json:"outputDir"`
// ExcludePatterns are regex patterns for files to exclude.
ExcludePatterns []string `json:"excludePatterns"`
// IncludePatterns are regex patterns for files to include.
IncludePatterns []string `json:"includePatterns"`
// KeepUnused keeps state even if not directly referenced.
KeepUnused bool `json:"keepUnused"`
// Aggressive enables more aggressive pruning.
Aggressive bool `json:"aggressive"`
// ReportFile is where to write the pruning report.
ReportFile string `json:"reportFile"`
}
PruningConfig configures state pruning behavior.
func DefaultPruningConfig ¶
func DefaultPruningConfig() PruningConfig
DefaultPruningConfig returns sensible defaults.
type PruningReport ¶
type PruningReport struct {
TotalStateVars int `json:"totalStateVars"`
UsedStateVars int `json:"usedStateVars"`
PrunedStateVars int `json:"prunedStateVars"`
EstimatedSavings int `json:"estimatedSavings"`
StateUsage map[string]StateUsage `json:"stateUsage"`
PrunedFiles []string `json:"prunedFiles"`
Errors []string `json:"errors,omitempty"`
}
PruningReport contains the results of state analysis.
func AnalyzeState ¶
func AnalyzeState(config PruningConfig) (*PruningReport, error)
AnalyzeState is a convenience function for state analysis.
func PruneState ¶
func PruneState(config PruningConfig) (*PruningReport, error)
PruneState is a convenience function for state pruning.
type Rune ¶
type Rune[T any] struct { // contains filtered or unexported fields }
Rune is the base reactive primitive, similar to Svelte's $state rune. It holds a value of type T and notifies all subscribers when the value changes.
func NewRune ¶
NewRune creates a new Rune with the given initial value. This is equivalent to Svelte's $state rune.
Example:
count := state.NewRune(0)
name := state.NewRune("hello")
func (*Rune[T]) Get ¶
func (r *Rune[T]) Get() T
Get returns the current value of the rune. This is thread-safe and can be called concurrently.
func (*Rune[T]) GetAny ¶
GetAny returns the current value of the rune as an interface{}. This implements the Observable interface.
func (*Rune[T]) MarshalJSON ¶
MarshalJSON implements json.Marshaler for serialization to client.
func (*Rune[T]) Set ¶
func (r *Rune[T]) Set(value T)
Set updates the rune's value and notifies all subscribers. If the value is being batched, notification is deferred until the batch completes. This is thread-safe and can be called concurrently.
func (*Rune[T]) SetAny ¶
SetAny updates the rune's value from an interface{}. This implements the Settable interface.
func (*Rune[T]) Subscribe ¶
func (r *Rune[T]) Subscribe(fn Subscriber[T]) Unsubscribe
Subscribe registers a callback that will be called whenever the value changes. Returns an Unsubscribe function to remove the subscription.
Example:
unsub := count.Subscribe(func(v int) {
fmt.Println("Count changed to:", v)
})
defer unsub()
func (*Rune[T]) SubscribeAny ¶
func (r *Rune[T]) SubscribeAny(fn func(any)) Unsubscribe
SubscribeAny registers a type-erased callback. This implements the Observable interface.
type Serializable ¶
Serializable represents a value that can be serialized to JSON
type Settable ¶
type Settable interface {
Observable
SetAny(any) error
}
Settable extends Observable for types that can be updated.
type StateDiff ¶
type StateDiff struct {
ComponentID string `json:"componentId"`
Key string `json:"key"`
OldValue interface{} `json:"oldValue,omitempty"`
NewValue interface{} `json:"newValue"`
Timestamp int64 `json:"timestamp"`
}
StateDiff represents a change in state
func NewStateDiff ¶
NewStateDiff creates a new state diff
func (*StateDiff) MarshalJSON ¶
MarshalJSON serializes the diff to JSON
type StateMap ¶
type StateMap struct {
// OnChange is invoked when any state variable changes.
// DEADLOCK WARNING: OnChange must NOT call back into StateMap.Add, StateMap.Remove,
// or any method that acquires sm.mu. It is invoked inside a goroutine spawned by
// SubscribeAny, which runs after the mutex is released — but if your handler triggers
// a synchronous chain that calls back into Add/Remove on the SAME StateMap, you will
// deadlock. Safe operations inside OnChange: read sm.Get(), send on channels, call
// external callbacks. Unsafe: sm.Add(), sm.Remove(), sm.AddAny().
OnChange func(key string, value any)
// contains filtered or unexported fields
}
StateMap is a collection of generic observables for component state
func (*StateMap) Add ¶
func (sm *StateMap) Add(name string, obs Observable) *StateMap
Add adds an observable to the state collection
func (*StateMap) Get ¶
func (sm *StateMap) Get(name string) (Observable, bool)
Get retrieves an observable by name
func (*StateMap) MarshalJSON ¶
MarshalJSON serializes the state map to JSON
type StateMessage ¶
type StateMessage struct {
Type string `json:"type"` // "init", "update", "sync", "error"
ComponentID string `json:"componentId,omitempty"`
Key string `json:"key,omitempty"`
Value interface{} `json:"value,omitempty"`
State interface{} `json:"state,omitempty"`
Error string `json:"error,omitempty"`
Timestamp int64 `json:"timestamp"`
}
StateMessage represents a message sent between server and client
func NewErrorMessage ¶
func NewErrorMessage(componentID, errMsg string) *StateMessage
NewErrorMessage creates an error message
func NewInitMessage ¶
func NewInitMessage(componentID string, state interface{}) *StateMessage
NewInitMessage creates an initialization message
func NewSyncMessage ¶
func NewSyncMessage(componentID string, state interface{}) *StateMessage
NewSyncMessage creates a sync message
func NewUpdateMessage ¶
func NewUpdateMessage(componentID, key string, value interface{}) *StateMessage
NewUpdateMessage creates an update message
func ParseMessage ¶
func ParseMessage(data []byte) (*StateMessage, error)
ParseMessage parses a JSON message
func (*StateMessage) MarshalJSON ¶
func (m *StateMessage) MarshalJSON() ([]byte, error)
MarshalJSON serializes the message to JSON
type StatePruner ¶
type StatePruner struct {
// contains filtered or unexported fields
}
StatePruner analyzes and prunes unused state.
func NewStatePruner ¶
func NewStatePruner(config PruningConfig) *StatePruner
NewStatePruner creates a new state pruner.
func (*StatePruner) Analyze ¶
func (sp *StatePruner) Analyze() (*PruningReport, error)
Analyze scans the codebase for state usage.
func (*StatePruner) GetReport ¶
func (sp *StatePruner) GetReport() *PruningReport
GetReport returns the current pruning report.
func (*StatePruner) Prune ¶
func (sp *StatePruner) Prune() (*PruningReport, error)
Prune removes unused state from the codebase.
func (*StatePruner) WriteReport ¶
func (sp *StatePruner) WriteReport(path string) error
WriteReport writes the pruning report to a file.
type StateSnapshot ¶
type StateSnapshot struct {
ComponentID string `json:"componentId"`
State map[string]interface{} `json:"state"`
Timestamp int64 `json:"timestamp"`
}
StateSnapshot represents a snapshot of component state at a point in time
func NewSnapshot ¶
func NewSnapshot(componentID string, state map[string]interface{}) *StateSnapshot
NewSnapshot creates a new state snapshot
func (*StateSnapshot) MarshalJSON ¶
func (s *StateSnapshot) MarshalJSON() ([]byte, error)
MarshalJSON serializes the snapshot to JSON
type StateTree ¶
type StateTree struct {
Name string `json:"name"`
Type string `json:"type"`
Size int `json:"size"`
Children map[string]*StateTree `json:"children,omitempty"`
Used bool `json:"used"`
Path string `json:"path"`
}
StateTree represents a hierarchical state structure for analysis.
func BuildStateTree ¶
BuildStateTree builds a hierarchical representation of state.
type StateUsage ¶
type StateUsage struct {
Name string `json:"name"`
File string `json:"file"`
Line int `json:"line"`
Type string `json:"type"`
References []string `json:"references,omitempty"`
IsExported bool `json:"isExported"`
IsUsed bool `json:"isUsed"`
CanPrune bool `json:"canPrune"`
}
StateUsage represents how a state variable is used.
type StateValidator ¶
type StateValidator struct {
// contains filtered or unexported fields
}
StateValidator validates state values
func NewStateValidator ¶
func NewStateValidator() *StateValidator
NewStateValidator creates a new state validator
func (*StateValidator) AddValidator ¶
func (sv *StateValidator) AddValidator(key string, v Validator)
AddValidator adds a validator for a key
func (*StateValidator) Validate ¶
func (sv *StateValidator) Validate(key string, value interface{}) error
Validate validates a value for a key
func (*StateValidator) ValidateAll ¶
func (sv *StateValidator) ValidateAll(values map[string]interface{}) error
ValidateAll validates all values in a map
type Subscriber ¶
type Subscriber[T any] func(T)
Subscriber is a callback function that receives value updates.
type Unsubscribe ¶
type Unsubscribe func()
Unsubscribe is a function returned by Subscribe to remove the subscription.
func Watch ¶
func Watch[T any](r *Rune[T], callback func(T)) Unsubscribe
Watch creates an effect that watches a single rune and calls a callback with its value.
Example:
count := state.NewRune(0)
unsub := state.Watch(count, func(v int) {
fmt.Println("Count changed to:", v)
})
defer unsub()