Help us shape the future of Go! Take the 2026 Go Developer Survey

vormia-go-core

module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: MIT

README

Vormia Go Core

Foundation layer for the Vormia ecosystem — dependency injection, config, logging, events, lifecycle, context, and errors.

Status: stable v1.1.0 (versioned by git tags).
Module: github.com/vormialabs/vormia-go-core · Go: 1.21+ · Deps: standard library only.

Deep dive: aiguide/GUIDE.md · AI / contributor rules: aiguide/CURSOR_CODEX_MCP_GUIDE.md · Changes: CHANGELOG.md

What it provides

Package Purpose
container DI: singleton + transient services, ServiceProvider
config Key/value store from .env / OS env; typed getters + Prefixed
logger Leveled logging (stdout, writer, or file)
events Sync + async publish/subscribe
lifecycle App with ordered boot/shutdown hooks and signal handling
context Typed context keys + mutable request ValueBag
errors Wrap / Exception / pluggable Handler

No HTTP, database, or cache here — those belong in higher Vormia layers.

Installation

go get github.com/vormialabs/vormia-go-core@v1.1.0

Requires the tag to be published on the remote. Check available versions:

git ls-remote --tags https://github.com/vormialabs/vormia-go-core.git
go list -m -versions github.com/vormialabs/vormia-go-core

Quick start

Full Vormia app (copy this)

End-state main.go after wiring core with vormia-go, Chi, and a database. PostgreSQL is the default. Also go get the framework and drivers you use:

go get github.com/vormialabs/vormia-go
go get github.com/vormialabs/vormia-go-driver-chi
go get github.com/vormialabs/vormia-go-driver-postgresql
# optional swaps: ...-mysql | ...-sqlite | ...-redis

Remove pieces you do not need (e.g. drop DB/WithDB for a router-only app). Kernel apps: do not also call lifecycle.WaitForSignal — k.Run owns SIGINT/SIGTERM.

package main

import (
	"net/http"

	chi "github.com/vormialabs/vormia-go-driver-chi"
	postgres "github.com/vormialabs/vormia-go-driver-postgresql"

	"github.com/vormialabs/vormia-go-core/config"
	"github.com/vormialabs/vormia-go-core/logger"
	"github.com/vormialabs/vormia-go/app"
	"github.com/vormialabs/vormia-go/contract"
)

func main() {
	// Load .env, then OS env (OS wins if both set)
	cfg := config.New()
	_ = cfg.Load(".env")
	cfg.LoadEnv()

	log := logger.NewStdoutLogger()
	log.SetLevel(logger.ParseLevel(cfg.GetString("LOG_LEVEL", "info")))
	log.Info("starting", map[string]interface{}{
		"app": cfg.GetString("APP_NAME", "storage-engine"),
	})

	db, err := postgres.Open(postgres.Config{
		Host:     cfg.GetString("DB_HOST", "localhost"),
		User:     cfg.GetString("DB_USER", "app"),
		Password: cfg.GetString("DB_PASSWORD", "secret"),
		Database: cfg.GetString("DB_NAME", "storage-engine"),
	})
	if err != nil {
		panic(err)
	}

	k := app.New(chi.New(), app.WithDB(db))

	k.Routes(func(r contract.Router) {
		r.Get("/health", func(w http.ResponseWriter, _ *http.Request) {
			_, _ = w.Write([]byte("ok"))
		})
	})

	if err := k.Run(":8080"); err != nil {
		panic(err)
	}
}
Swap database

Replace the Postgres import + Open block. The rest of main stays the same; handlers still see contract.Database.

MySQL
import mysql "github.com/vormialabs/vormia-go-driver-mysql"

db, err := mysql.Open(mysql.Config{
	Host:     cfg.GetString("DB_HOST", "localhost"),
	User:     cfg.GetString("DB_USER", "app"),
	Password: cfg.GetString("DB_PASSWORD", "secret"),
	Database: cfg.GetString("DB_NAME", "app"),
})
SQLite
import sqlite "github.com/vormialabs/vormia-go-driver-sqlite"

db, err := sqlite.Open(sqlite.Config{
	Path:        cfg.GetString("DB_PATH", "app.db"),
	ForeignKeys: true,
})
Optional Redis cache

Add alongside the database (or alone with app.WithCache only):

import redis "github.com/vormialabs/vormia-go-driver-redis"

cache, err := redis.Open(redis.Config{
	Addr: cfg.GetString("REDIS_ADDR", "localhost:6379"),
})
if err != nil {
	panic(err)
}

k := app.New(chi.New(), app.WithDB(db), app.WithCache(cache))
Core-only shell (no HTTP/DB)

Thin main using only this package: create App, load config, register hooks, boot, wait for signal. Use this when you are not running the vormia-go kernel.

package main

import (
	"os"

	"github.com/vormialabs/vormia-go-core/lifecycle"
	"github.com/vormialabs/vormia-go-core/logger"
)

func main() {
	app := lifecycle.NewApp()

	// File first, then OS env (OS wins).
	_ = app.Config().Load(".env") // missing file is OK
	app.Config().LoadEnv()

	app.Logger().SetLevel(logger.ParseLevel(
		app.Config().GetString("LOG_LEVEL", "info"),
	))

	app.OnBoot(func() error {
		return app.Config().Require("APP_NAME")
	})
	app.OnShutdown(func() error {
		// close pools, flush loggers, etc.
		return nil
	})

	if err := app.Boot(); err != nil {
		app.Logger().Error("boot failed", err, nil)
		os.Exit(1)
	}

	// Blocks on SIGINT/SIGTERM, then runs Shutdown in reverse order.
	_ = app.WaitForSignal()
}
Dependency injection
c := container.New()

// Singleton — factory runs once.
_ = c.Register("userRepo", func() interface{} {
    return &UserRepository{}
})

// Transient — fresh instance every Resolve.
_ = c.RegisterTransient("requestID", func() interface{} {
    return newID()
})

repo, err := c.Resolve("userRepo")
if err != nil {
    // match *container.ErrNotFound / *container.ErrCyclicDependency with errors.As
    panic(err)
}
userRepo := repo.(*UserRepository)

Wire modules with ServiceProvider (Register then Boot via RegisterProviders).

Configuration
cfg := config.New()
_ = cfg.Load(".env")
cfg.LoadEnv()

port := cfg.GetInt("PORT", 8080)
debug := cfg.GetBool("APP_DEBUG", false)
name := cfg.GetString("APP_NAME", "vormia")
err := cfg.Require("DB_HOST", "DB_USER") // one error listing all missing keys

// Prefixed is generic — any prefix (DB_, CACHE_, MAIL_, …). Prefix is stripped from keys.
group := cfg.Prefixed("DB_MYSQL2_") // map[string]string{"HOST": "...", "PORT": "3306", ...}

Typed getters never fail — they return the default when missing or unparsable. Prefixed only includes keys that start with the given prefix (bare keys like DB_HOST never appear under Prefixed("DB_MYSQL2_")).

Logging
log := logger.NewStdoutLogger()
log.SetLevel(logger.LevelDebug)

log.Info("started", map[string]interface{}{"version": "0.0.1"})
log.Error("failed", err, map[string]interface{}{"user_id": 123})

// Production: append to file and mirror to stdout.
fileLog, _ := logger.NewFileLogger("logs/app.log")
defer fileLog.Close()

Depend on the logger.Logger interface, not a concrete type. Also available: NewWriterLogger(w) for tests.

Events
d := events.NewDispatcher()

d.OnPanic(func(event string, recovered interface{}) {
    // required if you use DispatchAsync — otherwise panics are silent
})

d.Listen("user.created", func(payload interface{}) {
    // sync handlers run in registration order
})

d.Dispatch("user.created", user)

w := d.DispatchAsync("user.created", user)
w.Wait() // optional
Context values
import vctx "github.com/vormialabs/vormia-go-core/context"

const UserIDKey = vctx.Key("user_id")

ctx := vctx.WithValue(vctx.Background(), UserIDKey, "u-42")

bag := vctx.NewValueBag()
ctx = vctx.WithBag(ctx, bag)
if b := vctx.BagFrom(ctx); b != nil {
    b.Set("trace_id", "abc")
}
Errors
err := errors.Wrap(cause, "loading user")

ex := errors.NewException("payment failed", "PAY_DECLINED").
    WithCause(gatewayErr).
    With("order_id", 1234)

handler := errors.NewLoggingHandler(app.Logger())
handler.Handle(ex)

Core contracts

type ServiceContainer interface {
    Register(name string, factory func() interface{}) error
    Resolve(name string) (interface{}, error)
    Get(name string, defaultValue interface{}) interface{}
}

type Logger interface {
    Debug(message string, context map[string]interface{})
    Info(message string, context map[string]interface{})
    Warn(message string, context map[string]interface{})
    Error(message string, err error, context map[string]interface{})
    SetLevel(level Level)
}

type EventDispatcher interface {
    Listen(event string, callback func(payload interface{}))
    Dispatch(event string, payload interface{})
}

Also notable:

  • container: RegisterTransient, Has, ServiceProvider, RegisterProviders
  • config: Load / LoadEnv, typed getters, Require, All, Has
  • logger: ParseLevel, NewFileLogger, NewWriterLogger
  • events: OnPanic, ListenTo, DispatchAsync, Forget, HasListeners
  • lifecycle: OnBoot / OnShutdown (+ priority variants), WaitForSignal

Design notes

  • Thread-safe by default — container, config, dispatcher, loggers, value bag, hooks.
  • Fail fast on boot — first boot-hook error aborts; shutdown runs every hook and joins errors.
  • Config load order — Load(".env") then LoadEnv() so OS wins.
  • No magic — string-keyed services and explicit factories (no reflection autowiring).

Package layout

vormia-go-core/
├── go.mod
├── README.md
├── aiguide/           # GUIDE.md + AI contributor rules
├── container/
├── config/
├── logger/
├── events/
├── lifecycle/
├── context/
└── errors/

Development

go build ./...
go vet ./...
gofmt -l .                 # must print nothing
go test ./...
go test -race -count=2 ./...

License

See LICENSE.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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