state

package
v0.1.7 Latest Latest
Warning

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

Go to latest
Published: Mar 2, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

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

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

func BatchError(fn func() error) error

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

func BatchWithContext(ctx context.Context, fn func() error) error

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

func CalculateTreeSize(tree *StateTree) int

CalculateTreeSize calculates the estimated size of a state tree.

func FlushPendingNotifications added in v0.1.5

func FlushPendingNotifications(ctx context.Context)

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

func SerializePrunedState(state map[string]any, usedPaths map[string]bool) map[string]any

SerializePrunedState serializes only the used portions of state.

func SerializeState

func SerializeState(runes map[string]interface{}) ([]byte, error)

SerializeState serializes multiple runes into a JSON object

Types

type CleanupFunc

type CleanupFunc func()

CleanupFunc is returned by effects to clean up resources

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 Derived2

func Derived2[A, B, T any](a *Rune[A], b *Rune[B], combine func(A, B) T) *Derived[T]

Derived2 creates a derived value from two runses with a combine function.

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

func NewDerived[T any](compute func() T) *Derived[T]

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

func (d *Derived[T]) GetAny() any

GetAny returns the current value of the derivative as an interface{}. This implements the Observable interface.

func (*Derived[T]) ID

func (d *Derived[T]) ID() string

ID returns the unique identifier for this derived value

func (*Derived[T]) MarshalJSON

func (d *Derived[T]) MarshalJSON() ([]byte, error)

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

func NewEffect(fn EffectFn) *Effect

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

func (*Effect) IsActive

func (e *Effect) IsActive() bool

IsActive returns whether the effect is currently active

func (*Effect) Pause

func (e *Effect) Pause()

Pause temporarily stops the effect from running

func (*Effect) Resume

func (e *Effect) Resume()

Resume reactivates a paused effect

type EffectFn

type EffectFn func() CleanupFunc

EffectFn is the function passed to an effect

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

func NewRune[T any](initial T) *Rune[T]

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

func (r *Rune[T]) GetAny() any

GetAny returns the current value of the rune as an interface{}. This implements the Observable interface.

func (*Rune[T]) ID

func (r *Rune[T]) ID() string

ID returns the unique identifier for this rune.

func (*Rune[T]) MarshalJSON

func (r *Rune[T]) MarshalJSON() ([]byte, error)

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

func (r *Rune[T]) SetAny(value any) error

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.

func (*Rune[T]) Update

func (r *Rune[T]) Update(fn func(T) T)

Update applies a function to the current value and sets the result. This is useful for updates that depend on the current value.

Example:

count.Update(func(v int) int {
    return v + 1
})

type Serializable

type Serializable interface {
	Serialize() ([]byte, error)
}

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

func NewStateDiff(componentID, key string, oldValue, newValue interface{}) *StateDiff

NewStateDiff creates a new state diff

func (*StateDiff) MarshalJSON

func (d *StateDiff) MarshalJSON() ([]byte, error)

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 NewStateMap

func NewStateMap() *StateMap

NewStateMap creates a new state collection

func (*StateMap) Add

func (sm *StateMap) Add(name string, obs Observable) *StateMap

Add adds an observable to the state collection

func (*StateMap) AddAny

func (sm *StateMap) AddAny(name string, value interface{}) *StateMap

AddAny adds any primitive value as a rune to the state collection

func (*StateMap) ForEach

func (sm *StateMap) ForEach(fn func(key string, value any))

ForEach iterates over all observables in the state map

func (*StateMap) Get

func (sm *StateMap) Get(name string) (Observable, bool)

Get retrieves an observable by name

func (*StateMap) MarshalJSON

func (sm *StateMap) MarshalJSON() ([]byte, error)

MarshalJSON serializes the state map to JSON

func (*StateMap) ToJSON

func (sm *StateMap) ToJSON() (string, error)

ToJSON returns the state as a JSON string

func (*StateMap) ToMap

func (sm *StateMap) ToMap() map[string]any

ToMap returns all state values as a plain map

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

func BuildStateTree(state map[string]any) *StateTree

BuildStateTree builds a hierarchical representation of state.

func PruneStateTree

func PruneStateTree(tree *StateTree, usedPaths map[string]bool) *StateTree

PruneStateTree removes unused branches from a state tree.

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()

func Watch2

func Watch2[A, B any](a *Rune[A], b *Rune[B], callback func(A, B)) Unsubscribe

Watch2 creates an effect that watches two runes.

func Watch3

func Watch3[A, B, C any](a *Rune[A], b *Rune[B], c *Rune[C], callback func(A, B, C)) Unsubscribe

Watch3 creates an effect that watches three runes.

type Validator

type Validator func(interface{}) error

ValidateState validates state against a schema

Jump to

Keyboard shortcuts

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