foundation API

foundation

package

API reference for the foundation package.

S
struct

Container

Container wraps the foundation DI container with component-scoping.

foundation/foundation.go:22-24
type Container struct

Methods

Scope
Method

Scope creates a child container bound to a component lifecycle.

Returns

func (*Container) Scope() *Container
{
	return &Container{Container: c.Container.Scope()}
}
F
function

NewContainer

NewContainer creates a root DI container for the application.

Returns

foundation/foundation.go:27-29
func NewContainer() *Container

{
	return &Container{fndi.New()}
}
S
struct

EventBus

EventBus wraps the foundation type-safe event bus.

foundation/foundation.go:39-41
type EventBus struct

Fields

Name Type Description
bus *fnevents.Bus
F
function

Subscribe

Subscribe registers a handler for a typed event.

Parameters

bus
priority
...fnevents.Priority
foundation/foundation.go:47-49
func Subscribe[T any](bus *EventBus, fn fnevents.Handler[T], priority ...fnevents.Priority)

{
	fnevents.Subscribe[T](bus.bus, fn, priority...)
}
F
function

Emit

Emit sends a typed event synchronously.

Parameters

bus
event
T

Returns

error
foundation/foundation.go:52-54
func Emit[T any](bus *EventBus, event T) error

{
	return fnevents.Emit(context.Background(), bus.bus, event)
}
F
function

EmitAsync

EmitAsync sends a typed event without waiting for handlers.

Parameters

bus
event
T
foundation/foundation.go:57-61
func EmitAsync[T any](bus *EventBus, event T)

{
	if err := fnevents.EmitAsync(context.Background(), bus.bus, event); err != nil {
		log.Printf("foundation async event: %v", err)
	}
}
S
struct

Lifecycle

Lifecycle exposes foundation hooks for component lifecycle.

foundation/foundation.go:66-68
type Lifecycle struct

Methods

Before
Method

Before registers a pre-action hook (e.g., BeforeMount, BeforeUpdate).

Parameters

key string
func (*Lifecycle) Before(key string, fn fnhooks.HookFunc)
{
	l.runner.Before(key, fn)
}
After
Method

After registers a post-action hook (e.g., AfterMount, AfterUpdate).

Parameters

key string
func (*Lifecycle) After(key string, fn fnhooks.HookFunc)
{
	l.runner.After(key, fn)
}
Run
Method

Run executes an action surrounded by registered hooks.

Parameters

key string
action func() error
args ...any

Returns

error
func (*Lifecycle) Run(ctx context.Context, key string, action func() error, args ...any) error
{
	return l.runner.Run(ctx, key, action, args...)
}
Discover
Method

Discover scans obj via reflection for methods prefixed with prefix and auto-registers them as hooks. Not yet wired to foundation hooks discovery.

Parameters

any
string

Returns

func (*Lifecycle) Discover(any, string) *Lifecycle
{
	// placeholder: will wire to foundation hooks.Discovery when API stabilizes
	return l
}

Fields

Name Type Description
runner *fnhooks.Runner
F
function

NewLifecycle

NewLifecycle creates a lifecycle hook runner backed by reflection.

Returns

foundation/foundation.go:71-73
func NewLifecycle() *Lifecycle

{
	return &Lifecycle{runner: fnhooks.NewRunner()}
}
S
struct

EffectPipeline

EffectPipeline chains middleware over signal-driven side effects.

foundation/foundation.go:100-102
type EffectPipeline struct

Methods

Use
Method

Use registers middleware. Middleware receives EffectInput and can call next.

func (*EffectPipeline) Use(mw fnpipeline.Middleware[EffectInput, struct{}])
{
	if ep.pip == nil {
		return
	}
	ep.pip.Use(mw)
}
Process
Method

Process executes the pipeline for a signal change.

Parameters

func (*EffectPipeline) Process(ctx context.Context, in EffectInput)
{
	_, _ = ep.pip.Process(ctx, in)
}

Fields

Name Type Description
pip *fnpipeline.Pipeline[EffectInput, struct{}]
S
struct

EffectInput

EffectInput carries what changed and the component ID.

foundation/foundation.go:105-109
type EffectInput struct

Fields

Name Type Description
ComponentID string
SignalName string
Value any
F
function

NewEffectPipeline

NewEffectPipeline builds a pipeline with a no-op default handler.

Returns

foundation/foundation.go:112-119
func NewEffectPipeline() *EffectPipeline

{
	p := fnpipeline.New[EffectInput, struct{}]()
	// default handler does nothing
	p.Then(func(_ context.Context, _ EffectInput) (struct{}, error) {
		return struct{}{}, nil
	})
	return &EffectPipeline{pip: p}
}
T
type

Result

Result re-exports foundation’s Result monad for async UI ops.

foundation/foundation.go:137-137
type Result fnresult.Result[T]
F
function

Ok

Ok creates a successful Result.

Parameters

v
T

Returns

Result[T]
foundation/foundation.go:140-140
func Ok[T any](v T) Result[T]

{ return fnresult.Ok[T](v) }
F
function

Err

Err creates a failed Result.

Parameters

e
error

Returns

Result[T]
foundation/foundation.go:143-143
func Err[T any](e error) Result[T]

{ return fnresult.Err[T](e) }
T
type

Option

Option is a functional option for composition configuration.

foundation/foundation.go:148-148
type Option func(o *T)
F
function

Apply

Apply runs opts against target.

Parameters

target
*T
opts
...Option[T]
foundation/foundation.go:151-157
func Apply[T any](target *T, opts ...Option[T])

{
	for _, o := range opts {
		if o != nil {
			o(target)
		}
	}
}
S
struct

tagScanner

foundation/foundation.go:165-165
type tagScanner struct

Methods

Scan
Method

Scan extracts Meta from the given struct (pointer to struct).

Parameters

v any

Returns

*Meta
error
func (*tagScanner) Scan(v any) (*Meta, error)
{
	typ := reflect.TypeOf(v)
	if typ.Kind() == reflect.Pointer {
		typ = typ.Elem()
	}
	if typ.Kind() != reflect.Struct {
		return nil, fmt.Errorf("rfw tag scanner: expected struct, got %s", typ.Kind())
	}

	m := &Meta{}
	for i := 0; i < typ.NumField(); i++ {
		field := typ.Field(i)
		tag, ok := field.Tag.Lookup("rfw")
		if !ok {
			continue
		}
		if tag == "" {
			continue
		}
		ts.parseTag(field, tag, m)
	}
	return m, nil
}
parseTag
Method

Parameters

tag string
m *Meta
func (*tagScanner) parseTag(field reflect.StructField, tag string, m *Meta)
{
	// Shorthand with empty value means the directive itself is the marker.
	switch tag {
	case "signal":
		m.Signals = append(m.Signals, SignalMeta{Field: field, Name: field.Name})
		return
	case "prop":
		m.Props = append(m.Props, PropMeta{Field: field, Name: field.Name})
		return
	case "ref":
		m.Refs = append(m.Refs, field.Name)
		return
	case "inject":
		m.Injects = append(m.Injects, InjectMeta{Field: field, Key: field.Name})
		return
	}

	// key:value pairs
	parts := splitTag(tag)
	switch parts[0] {
	case "store":
		if len(parts) > 1 {
			m.Stores = append(m.Stores, StoreMeta{Field: field, Name: parts[1]})
		}
	case "host":
		if len(parts) > 1 {
			m.Hosts = append(m.Hosts, HostMeta{Field: field, Name: parts[1]})
		}
	case "event":
		if len(parts) >= 3 {
			modifiers := []string{}
			if len(parts) > 3 {
				modifiers = parts[3:]
			}
			m.Events = append(m.Events, EventMeta{
				Field: field, DOMEvent: parts[1], Handler: parts[2], Modifiers: modifiers,
			})
		}
	case "inject":
		key := field.Name
		if len(parts) > 1 {
			key = parts[1]
		}
		m.Injects = append(m.Injects, InjectMeta{Field: field, Key: key})
	case "fsm":
		if len(parts) > 1 {
			m.FSMs = append(m.FSMs, FSMMeta{Field: field, Definition: parts[1]})
		}
	case "history":
		if len(parts) >= 4 {
			m.Histories = append(m.Histories, HistoryMeta{
				Field: field, Store: parts[1], UndoEvt: parts[2], RedoEvt: parts[3],
			})
		}
	}
}
S
struct

Meta

Meta holds all rfw tag metadata extracted from a single struct type.

foundation/foundation.go:168-178
type Meta struct

Fields

Name Type Description
Signals []SignalMeta
Stores []StoreMeta
Props []PropMeta
Refs []string
Hosts []HostMeta
Events []EventMeta
Injects []InjectMeta
FSMs []FSMMeta
Histories []HistoryMeta
S
struct

SignalMeta

SignalMeta describes a signal field.

foundation/foundation.go:181-184
type SignalMeta struct

Fields

Name Type Description
Field reflect.StructField
Name string
S
struct

StoreMeta

StoreMeta describes a store field.

foundation/foundation.go:187-190
type StoreMeta struct

Fields

Name Type Description
Field reflect.StructField
Name string
S
struct

PropMeta

PropMeta describes a component property field.

foundation/foundation.go:193-197
type PropMeta struct

Fields

Name Type Description
Field reflect.StructField
Name string
DefaultVal any
S
struct

HostMeta

HostMeta describes a host binding field.

foundation/foundation.go:200-203
type HostMeta struct

Fields

Name Type Description
Field reflect.StructField
Name string
S
struct

EventMeta

EventMeta describes an event binding field.

foundation/foundation.go:206-211
type EventMeta struct

Fields

Name Type Description
Field reflect.StructField
DOMEvent string
Handler string
Modifiers []string
S
struct

InjectMeta

InjectMeta describes a dependency injection field.

foundation/foundation.go:214-217
type InjectMeta struct

Fields

Name Type Description
Field reflect.StructField
Key string
S
struct

FSMMeta

FSMMeta describes a finite-state machine field.

foundation/foundation.go:220-223
type FSMMeta struct

Fields

Name Type Description
Field reflect.StructField
Definition string
S
struct

HistoryMeta

HistoryMeta describes a history field and its events.

foundation/foundation.go:226-231
type HistoryMeta struct

Fields

Name Type Description
Field reflect.StructField
Store string
UndoEvt string
RedoEvt string
F
function

splitTag

splitTag splits a colon-separated tag safely.

Parameters

tag
string

Returns

[]string
foundation/foundation.go:316-327
func splitTag(tag string) []string

{
	var parts []string
	start := 0
	for i := 0; i < len(tag); i++ {
		if tag[i] == ':' {
			parts = append(parts, tag[start:i])
			start = i + 1
		}
	}
	parts = append(parts, tag[start:])
	return parts
}
F
function

TestContainerScope

Parameters

foundation/foundation_test.go:10-22
func TestContainerScope(t *testing.T)

{
	root := NewContainer()
	root.Provide("name", "root")
	child := root.Scope()
	child.Provide("local", 7)

	if got, ok := child.Get("name"); !ok || got != "root" {
		t.Fatalf("expected scoped container to resolve parent value, got %v %v", got, ok)
	}
	if got, ok := child.Get("local"); !ok || got != 7 {
		t.Fatalf("expected scoped local value, got %v %v", got, ok)
	}
}
F
function

TestEventsAndLifecycle

Parameters

foundation/foundation_test.go:24-59
func TestEventsAndLifecycle(t *testing.T)

{
	bus := &EventBus{bus: DefaultBus.bus}
	type event struct{ Value int }
	var seen int
	Subscribe[event](bus, func(_ context.Context, e event) error {
		seen = e.Value
		return nil
	})
	if err := Emit[event](bus, event{Value: 3}); err != nil {
		t.Fatalf("emit failed: %v", err)
	}
	if seen != 3 {
		t.Fatalf("expected event value 3, got %d", seen)
	}

	lc := NewLifecycle()
	order := []string{}
	lc.Before("mount", func(_ context.Context, key string, _ []any) error {
		order = append(order, "before:"+key)
		return nil
	})
	lc.After("mount", func(_ context.Context, key string, _ []any) error {
		order = append(order, "after:"+key)
		return nil
	})
	if err := lc.Run(context.Background(), "mount", func() error {
		order = append(order, "action")
		return nil
	}); err != nil {
		t.Fatalf("lifecycle run failed: %v", err)
	}
	want := []string{"before:mount", "action", "after:mount"}
	if !reflect.DeepEqual(order, want) {
		t.Fatalf("expected lifecycle order %v, got %v", want, order)
	}
}
F
function

TestEffectPipelineAndResult

Parameters

foundation/foundation_test.go:61-80
func TestEffectPipelineAndResult(t *testing.T)

{
	ep := NewEffectPipeline()
	called := false
	ep.Use(func(ctx context.Context, input EffectInput, next func(context.Context, EffectInput) (struct{}, error)) (struct{}, error) {
		called = input.ComponentID == "cmp" && input.SignalName == "count" && input.Value == 1
		return next(ctx, input)
	})
	ep.Process(context.Background(), EffectInput{ComponentID: "cmp", SignalName: "count", Value: 1})
	if !called {
		t.Fatalf("expected middleware to observe effect input")
	}

	if got := Ok(5).UnwrapOr(0); got != 5 {
		t.Fatalf("expected ok value 5, got %d", got)
	}
	errResult := Err[int](errors.New("boom"))
	if !errResult.IsErr() || errResult.UnwrapOr(9) != 9 {
		t.Fatalf("expected err result fallback")
	}
}
F
function

TestApplyAndTagScanner

Parameters

foundation/foundation_test.go:82-116
func TestApplyAndTagScanner(t *testing.T)

{
	type options struct{ Enabled bool }
	cfg := options{}
	Apply(&cfg, func(o *options) { o.Enabled = true })
	if !cfg.Enabled {
		t.Fatalf("expected option applied")
	}

	type tagged struct {
		Count string `rfw:"signal"`
		Store string `rfw:"store:cart"`
		Ref   string `rfw:"ref"`
		Click string `rfw:"event:click:Save:prevent:stop"`
		Hist  string `rfw:"history:main:undo:redo"`
	}
	meta, err := TagScanner.Scan(tagged{})
	if err != nil {
		t.Fatalf("scan failed: %v", err)
	}
	if len(meta.Signals) != 1 || meta.Signals[0].Name != "Count" {
		t.Fatalf("unexpected signals: %+v", meta.Signals)
	}
	if len(meta.Stores) != 1 || meta.Stores[0].Name != "cart" {
		t.Fatalf("unexpected stores: %+v", meta.Stores)
	}
	if len(meta.Refs) != 1 || meta.Refs[0] != "Ref" {
		t.Fatalf("unexpected refs: %+v", meta.Refs)
	}
	if len(meta.Events) != 1 || meta.Events[0].DOMEvent != "click" || meta.Events[0].Handler != "Save" || len(meta.Events[0].Modifiers) != 2 {
		t.Fatalf("unexpected events: %+v", meta.Events)
	}
	if len(meta.Histories) != 1 || meta.Histories[0].Store != "main" {
		t.Fatalf("unexpected histories: %+v", meta.Histories)
	}
}