Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/lokstra/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import (
"path/filepath"
"slices"

"github.com/primadi/lokstra"
"github.com/primadi/lokstra/common/logger"
"github.com/primadi/lokstra/common/utils"
"github.com/primadi/lokstra/core/annotation"
"github.com/primadi/lokstra/lokstra_init"
)

const version = "1.0.2"
Expand All @@ -19,7 +19,7 @@ func main() {
logger.SetLogLevel(logger.LogLevelInfo)

// for debugging purpose
if lokstra.DetectRunMode() != lokstra.RunModeProd {
if lokstra_init.DetectRunMode() != lokstra_init.RunModeProd {
// use 04_sync_config template for testing
os.Chdir(filepath.Join(utils.GetBasePath(), "../../project_templates/02_app_framework/04_sync_config"))
// os.Args = slices.Concat(os.Args[:1], []string{"migration", "status"})
Expand Down
3 changes: 1 addition & 2 deletions cmd/lokstra/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"path/filepath"
"time"

"github.com/primadi/lokstra"
"github.com/primadi/lokstra/common/utils"
"github.com/primadi/lokstra/lokstra_registry"
"github.com/primadi/lokstra/serviceapi"
Expand Down Expand Up @@ -124,7 +123,7 @@ func executeMigration(subCmd, configFile, migrationDir, dbPoolName string, steps
}

// load named-db-pools from config file
if err := lokstra.LoadConfig(cfgFile); err != nil {
if err := lokstra_registry.LoadConfig(cfgFile); err != nil {
return fmt.Errorf("failed to load config file '%s': %w", filepath.Base(cfgFile), err)
}

Expand Down
6 changes: 2 additions & 4 deletions core/annotation/internal/multifile_test/main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package main

import (
"github.com/primadi/lokstra"
)
import "github.com/primadi/lokstra/lokstra_init"

func main() {
lokstra.Bootstrap()
lokstra_init.Bootstrap()
}
4 changes: 2 additions & 2 deletions core/app/listener/fasthttp/fasthttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ func (s *FastHttp) ListenAndServe() error {
if err != nil {
return fmt.Errorf("failed to listen on unix socket: %w", err)
}
logger.LogInfo("[FastHttp] Starting server on Unix socket %s\n", socketPath)
// logger.LogInfo("[FastHttp] Starting server on Unix socket %s\n", socketPath)
} else {
var err error
listener, err = net.Listen("tcp", s.addr)
if err != nil {
return listener_utils.WrapListenError(s.addr, err)
}
logger.LogInfo("[FastHttp] Starting server on TCP %s\n", s.addr)
// logger.LogInfo("[FastHttp] Starting server on TCP %s\n", s.addr)
}

if s.secure {
Expand Down
4 changes: 2 additions & 2 deletions core/app/listener/net_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ func (s *NetHttp) ListenAndServe() error {
if err != nil {
return fmt.Errorf("failed to listen on unix socket: %w", err)
}
logger.LogInfo("[NETHTTP] Starting server on Unix socket %s\n", socketPath)
// logger.LogInfo("[NETHTTP] Starting server on Unix socket %s\n", socketPath)
} else {
var err error
listener, err = net.Listen("tcp", s.server.Addr)
if err != nil {
return listener_utils.WrapListenError(s.server.Addr, err)
}
logger.LogInfo("[NETHTTP] Starting server on TCP %s\n", s.server.Addr)
// logger.LogInfo("[NETHTTP] Starting server on TCP %s\n", s.server.Addr)
}

if s.secure {
Expand Down
55 changes: 8 additions & 47 deletions core/deploy/loader/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package loader

import (
"fmt"
"os"
"path/filepath"
"strings"
"time"

Expand Down Expand Up @@ -859,12 +857,12 @@ func RegisterDefinitionsForRuntime(registry *deploy.GlobalRegistry, config *sche
return nil
}

// LoadAndBuild loads config and builds ALL deployments into Global registry
// LoadConfig loads config and builds ALL deployments into Global registry
// Returns error only - deployments are stored in deploy.Global()
func LoadAndBuild(configPaths []string) error {
config, err := LoadConfig(configPaths...)
func LoadConfig(configPaths ...string) (*schema.DeployConfig, error) {
config, err := loadConfig(configPaths...)
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
return nil, fmt.Errorf("failed to load config: %w", err)
}

registry := deploy.Global()
Expand All @@ -878,7 +876,7 @@ func LoadAndBuild(configPaths []string) error {
// Store definitions to registry (NO runtime registration, just store data)
// Runtime registration will happen in RunCurrentServer
if err := StoreDefinitionsToRegistry(registry, config); err != nil {
return fmt.Errorf("failed to store definitions: %w", err)
return nil, fmt.Errorf("failed to store definitions: %w", err)
}

// Build ALL deployments (2-Layer Architecture: YAML -> Topology only)
Expand Down Expand Up @@ -978,7 +976,7 @@ func LoadAndBuild(configPaths []string) error {
// return fmt.Errorf("failed to setup named DB pools: %w", err)
// }

return nil
return config, nil
}

// LoadNamedDbPoolsFromConfig auto-discovers and sets up named DB pools from config
Expand Down Expand Up @@ -1084,48 +1082,11 @@ func LoadNamedDbPoolsFromConfig() error {
}

// Set DSN and Schema for poolName
dbPoolManager.SetNamedDbPool(poolName, dsn, schema, nil)

// Create the pool
dbPool, err := dbPoolManager.GetNamedDbPool(poolName)
if err != nil {
return fmt.Errorf("failed to create pool '%s': %w", poolName, err)
}

// Register pool as a service
registry.RegisterService(poolName, dbPool)
// This also auto-registers the pool as a lazy service
dbPoolManager.SetNamedDbPool(poolName, dsn, schema, poolConfig.RlsContext)

logger.LogDebug("✅ Registered DB pool: %s (schema: %s)", poolName, schema)
}

return nil
}

// LoadAndBuildFromDir loads all YAML files from a directory and builds ALL deployments
func LoadAndBuildFromDir(dirPath string) error {
// Scan directory for YAML files
entries, err := os.ReadDir(dirPath)
if err != nil {
return fmt.Errorf("failed to read directory: %w", err)
}

var paths []string
for _, entry := range entries {
if entry.IsDir() {
continue
}

name := entry.Name()
ext := filepath.Ext(name)
if ext == ".yaml" || ext == ".yml" {
paths = append(paths, filepath.Join(dirPath, name))
}
}

if len(paths) == 0 {
return fmt.Errorf("no YAML files found in directory: %s", dirPath)
}

// Delegate to LoadAndBuild
return LoadAndBuild(paths)
}
63 changes: 32 additions & 31 deletions core/deploy/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import (
"gopkg.in/yaml.v3"
)

// LoadConfig loads a deployment configuration from YAML file(s)
// loadConfig loads a deployment configuration from YAML file(s)
// Supports single file or multiple files that will be merged
func LoadConfig(paths ...string) (*schema.DeployConfig, error) {
// Paths can be files or folders - folders will be expanded to all *.yaml files
// This is a private function - external code should use LoadAndBuild instead
func loadConfig(paths ...string) (*schema.DeployConfig, error) {
if len(paths) == 0 {
return nil, fmt.Errorf("no config files specified")
}
Expand All @@ -26,16 +28,42 @@ func LoadConfig(paths ...string) (*schema.DeployConfig, error) {

basePath := utils.GetBasePath()

// STEP 1: Load and merge all files (RAW, no resolution yet)
// STEP 0: Expand folders to files
var expandedPaths []string
for _, path := range paths {
// If path is already absolute, use it directly; otherwise join with basePath
normPath := path
if !filepath.IsAbs(path) {
normPath = filepath.Join(basePath, path)
}

// Check if path is a directory
info, err := os.Stat(normPath)
if err != nil {
return nil, fmt.Errorf("failed to access %s: %w", path, err)
}

if info.IsDir() {
// Expand directory to *.yaml files
yamlFiles, err := filepath.Glob(filepath.Join(normPath, "*.yaml"))
if err != nil {
return nil, fmt.Errorf("failed to scan directory %s: %w", path, err)
}
if len(yamlFiles) == 0 {
return nil, fmt.Errorf("no YAML files found in directory: %s", path)
}
expandedPaths = append(expandedPaths, yamlFiles...)
} else {
// It's a file, use as is
expandedPaths = append(expandedPaths, normPath)
}
}

// STEP 1: Load and merge all files (RAW, no resolution yet)
for _, normPath := range expandedPaths {
config, err := loadSingleFileRaw(normPath)
if err != nil {
return nil, fmt.Errorf("failed to load %s: %w", path, err)
return nil, fmt.Errorf("failed to load %s: %w", normPath, err)
}

if merged == nil {
Expand Down Expand Up @@ -244,30 +272,3 @@ func ValidateConfig(config *schema.DeployConfig) error {

return nil
}

// LoadConfigFromDir loads all .yaml and .yml files from a directory and merges them
func LoadConfigFromDir(dirPath string) (*schema.DeployConfig, error) {
entries, err := os.ReadDir(dirPath)
if err != nil {
return nil, fmt.Errorf("failed to read directory: %w", err)
}

var paths []string
for _, entry := range entries {
if entry.IsDir() {
continue
}

name := entry.Name()
ext := filepath.Ext(name)
if ext == ".yaml" || ext == ".yml" {
paths = append(paths, filepath.Join(dirPath, name))
}
}

if len(paths) == 0 {
return nil, fmt.Errorf("no YAML files found in directory: %s", dirPath)
}

return LoadConfig(paths...)
}
2 changes: 1 addition & 1 deletion core/deploy/loader/test/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func TestLoadMultipleFiles(t *testing.T) {
}

func TestLoadFromDirectory(t *testing.T) {
config, err := loader.LoadConfigFromDir("testdata")
config, err := loader.LoadConfig("testdata")
if err != nil {
t.Fatalf("failed to load from directory: %v", err)
}
Expand Down
6 changes: 6 additions & 0 deletions core/deploy/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,12 @@ func (g *GlobalRegistry) RegisterService(name string, service any) {
logger.LogDebug("ℹ️ Registered service instance: '%s'\n", name)
}

// UnregisterService removes a service instance from the registry
func (g *GlobalRegistry) UnregisterService(name string) {
g.serviceInstances.Delete(name)
logger.LogDebug("ℹ️ Unregistered service instance: '%s'\n", name)
}

// RegisterLazyService registers a lazy service factory that will be instantiated on first access.
// The factory will be called only once, and the result is cached.
// This allows services to be registered in any order, regardless of dependencies.
Expand Down
3 changes: 2 additions & 1 deletion core/deploy/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ type DbPoolConfig struct {
Password string `yaml:"password,omitempty" json:"password,omitempty"`

// Schema configuration
Schema string `yaml:"schema,omitempty" json:"schema,omitempty"` // Default: "public"
Schema string `yaml:"schema,omitempty" json:"schema,omitempty"` // Default: "public"
RlsContext map[string]string `yaml:"rls-context,omitempty" json:"rls-context,omitempty"` // Row Level Security context variables

// Pool configuration (optional)
MinConns int `yaml:"min-conns,omitempty" json:"min-conns,omitempty"`
Expand Down
29 changes: 0 additions & 29 deletions core/service/lazy_load.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,35 +107,6 @@ func (l *Cached[T]) IsLoaded() bool {
return !utils.IsNil(l.cache)
}

// creates a lazy service loader from factory service configuration map.
func LazyLoadFromConfig[T any](cfg map[string]any, key string) *Cached[T] {
if cfg == nil {
return nil
}

val, ok := cfg[key]
if !ok {
return nil
}

if svcName, ok := val.(string); ok {
return LazyLoad[T](svcName)
}

// Not a valid service reference
return nil
}

// creates a lazy service loader from factory service configuration map.
// It panics if the key is missing or invalid.
func MustLazyLoadFromConfig[T any](cfg map[string]any, key string) *Cached[T] {
lazy := LazyLoadFromConfig[T](cfg, key)
if lazy == nil {
panic("missing required dependency '" + key + "'")
}
return lazy
}

// LazyLoadWith creates a lazy service loader with a custom loader function.
// The loader function is called on first Get() and the result is cached.
// This is useful for dependency injection frameworks that manage their own service resolution.
Expand Down
Loading