foundation
packageAPI reference for the foundation
package.
Imports
(11)context
STD
fmt
STD
log
STD
reflect
PKG
github.com/mirkobrombin/go-foundation/v2/app/di
PKG
github.com/mirkobrombin/go-foundation/v2/core/events
PKG
github.com/mirkobrombin/go-foundation/v2/core/hooks
PKG
github.com/mirkobrombin/go-foundation/v2/core/pipeline
PKG
github.com/mirkobrombin/go-foundation/v2/core/result
STD
errors
STD
testing
Container
Container wraps the foundation DI container with component-scoping.
type Container struct
NewContainer
NewContainer creates a root DI container for the application.
Returns
func NewContainer() *Container
{
return &Container{fndi.New()}
}
EventBus
EventBus wraps the foundation type-safe event bus.
type EventBus struct
Fields
| Name | Type | Description |
|---|---|---|
| bus | *fnevents.Bus |
Subscribe
Subscribe registers a handler for a typed event.
Parameters
func Subscribe[T any](bus *EventBus, fn fnevents.Handler[T], priority ...fnevents.Priority)
{
fnevents.Subscribe[T](bus.bus, fn, priority...)
}
Emit
Emit sends a typed event synchronously.
Parameters
Returns
func Emit[T any](bus *EventBus, event T) error
{
return fnevents.Emit(context.Background(), bus.bus, event)
}
EmitAsync
EmitAsync sends a typed event without waiting for handlers.
Parameters
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)
}
}
Lifecycle
Lifecycle exposes foundation hooks for component lifecycle.
type Lifecycle struct
Methods
Before registers a pre-action hook (e.g., BeforeMount, BeforeUpdate).
Parameters
func (*Lifecycle) Before(key string, fn fnhooks.HookFunc)
{
l.runner.Before(key, fn)
}
After registers a post-action hook (e.g., AfterMount, AfterUpdate).
Parameters
func (*Lifecycle) After(key string, fn fnhooks.HookFunc)
{
l.runner.After(key, fn)
}
Run executes an action surrounded by registered hooks.
Parameters
Returns
func (*Lifecycle) Run(ctx context.Context, key string, action func() error, args ...any) error
{
return l.runner.Run(ctx, key, action, args...)
}
Discover scans obj via reflection for methods prefixed with prefix and auto-registers them as hooks. Not yet wired to foundation hooks discovery.
Parameters
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 |
NewLifecycle
NewLifecycle creates a lifecycle hook runner backed by reflection.
Returns
func NewLifecycle() *Lifecycle
{
return &Lifecycle{runner: fnhooks.NewRunner()}
}
EffectPipeline
EffectPipeline chains middleware over signal-driven side effects.
type EffectPipeline struct
Methods
Use registers middleware. Middleware receives EffectInput and can call next.
Parameters
func (*EffectPipeline) Use(mw fnpipeline.Middleware[EffectInput, struct{}])
{
if ep.pip == nil {
return
}
ep.pip.Use(mw)
}
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{}] |
EffectInput
EffectInput carries what changed and the component ID.
type EffectInput struct
Fields
| Name | Type | Description |
|---|---|---|
| ComponentID | string | |
| SignalName | string | |
| Value | any |
NewEffectPipeline
NewEffectPipeline builds a pipeline with a no-op default handler.
Returns
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}
}
Result
Result re-exports foundation’s Result monad for async UI ops.
type Result fnresult.Result[T]
Ok
Ok creates a successful Result.
Parameters
Returns
func Ok[T any](v T) Result[T]
{ return fnresult.Ok[T](v) }
Err
Err creates a failed Result.
Parameters
Returns
func Err[T any](e error) Result[T]
{ return fnresult.Err[T](e) }
Option
Option is a functional option for composition configuration.
type Option func(o *T)
Apply
Apply runs opts against target.
Parameters
func Apply[T any](target *T, opts ...Option[T])
{
for _, o := range opts {
if o != nil {
o(target)
}
}
}
tagScanner
type tagScanner struct
Methods
Scan extracts Meta from the given struct (pointer to struct).
Parameters
Returns
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
}
Parameters
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],
})
}
}
}
Meta
Meta holds all rfw tag metadata extracted from a single struct type.
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 |
SignalMeta
SignalMeta describes a signal field.
type SignalMeta struct
Fields
| Name | Type | Description |
|---|---|---|
| Field | reflect.StructField | |
| Name | string |
StoreMeta
StoreMeta describes a store field.
type StoreMeta struct
Fields
| Name | Type | Description |
|---|---|---|
| Field | reflect.StructField | |
| Name | string |
PropMeta
PropMeta describes a component property field.
type PropMeta struct
Fields
| Name | Type | Description |
|---|---|---|
| Field | reflect.StructField | |
| Name | string | |
| DefaultVal | any |
HostMeta
HostMeta describes a host binding field.
type HostMeta struct
Fields
| Name | Type | Description |
|---|---|---|
| Field | reflect.StructField | |
| Name | string |
EventMeta
EventMeta describes an event binding field.
type EventMeta struct
Fields
| Name | Type | Description |
|---|---|---|
| Field | reflect.StructField | |
| DOMEvent | string | |
| Handler | string | |
| Modifiers | []string |
InjectMeta
InjectMeta describes a dependency injection field.
type InjectMeta struct
Fields
| Name | Type | Description |
|---|---|---|
| Field | reflect.StructField | |
| Key | string |
FSMMeta
FSMMeta describes a finite-state machine field.
type FSMMeta struct
Fields
| Name | Type | Description |
|---|---|---|
| Field | reflect.StructField | |
| Definition | string |
HistoryMeta
HistoryMeta describes a history field and its events.
type HistoryMeta struct
Fields
| Name | Type | Description |
|---|---|---|
| Field | reflect.StructField | |
| Store | string | |
| UndoEvt | string | |
| RedoEvt | string |
splitTag
splitTag splits a colon-separated tag safely.
Parameters
Returns
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
}
TestContainerScope
Parameters
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)
}
}
TestEventsAndLifecycle
Parameters
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)
}
}
TestEffectPipelineAndResult
Parameters
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")
}
}
TestApplyAndTagScanner
Parameters
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)
}
}