Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ func (m *UsersModule) Definition() module.ModuleDef {
Controllers: []module.ControllerDef{{
Name: "UsersController",
Build: func(r module.Resolver) (any, error) {
svc, _ := r.Get("users.service")
return NewUsersController(svc.(UsersService)), nil
svc, _ := module.Get[UsersService](r, "users.service")
return NewUsersController(svc), nil
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
},
}},
Exports: []module.Token{"users.service"},
Expand Down Expand Up @@ -168,7 +168,7 @@ See [Architecture Guide](docs/architecture.md) for details.
| Concept | NestJS | modkit |
|---------|--------|--------|
| Module definition | `@Module()` decorator | `ModuleDef` struct |
| Dependency injection | Constructor injection via metadata | Explicit `r.Get(token)` |
| Dependency injection | Constructor injection via metadata | Explicit `module.Get[T](r, token)` |
| Route binding | `@Get()`, `@Post()` decorators | `RegisterRoutes(router)` method |
| Middleware | `NestMiddleware` interface | `func(http.Handler) http.Handler` |
| Guards/Pipes/Interceptors | Framework abstractions | Standard Go middleware |
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,10 @@ stateDiagram-v2

```go
// First call: builds the provider
svc, _ := r.Get("users.service")
svc, _ := module.Get[UserService](r, "users.service")

// Second call: returns cached instance
svc2, _ := r.Get("users.service") // same instance as svc
svc2, _ := module.Get[UserService](r, "users.service") // same instance as svc
```

Cycles are detected at build time and return a `ProviderCycleError`.
Expand Down
4 changes: 2 additions & 2 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ modkit uses explicit `Build` functions and string tokens. Everything is visible
String tokens are simple, explicit, and work without reflection. The trade-off is manual type casting when you call `Get()`:

```go
svc, _ := r.Get("users.service")
userService := svc.(UsersService)
svc, _ := module.Get[UsersService](r, "users.service")
// svc is already of type UsersService
```

This is intentional—it keeps the framework small and makes dependencies visible.
Expand Down
8 changes: 4 additions & 4 deletions docs/guides/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,11 @@ func (m *AuthModule) Definition() module.ModuleDef {
{
Token: "auth.middleware",
Build: func(r module.Resolver) (any, error) {
svc, err := r.Get(TokenAuthService)
svc, err := module.Get[*AuthService](r, TokenAuthService)
if err != nil {
return nil, err
}
return JWTMiddleware(svc.(*AuthService).Secret()), nil
return JWTMiddleware(svc.Secret()), nil
},
},
},
Expand All @@ -222,8 +222,8 @@ Usage at startup:
```go
app, _ := kernel.Bootstrap(&AppModule{})

authMW, _ := app.Get("auth.middleware")
router.Use(authMW.(func(http.Handler) http.Handler))
authMW, _ := module.Get[func(http.Handler) http.Handler](app, "auth.middleware")
router.Use(authMW)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
```

## Optional Authentication
Expand Down
6 changes: 3 additions & 3 deletions docs/guides/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func main() {

| Aspect | Fx | modkit |
|--------|-----|--------|
| Injection | Automatic via reflection | Explicit via `r.Get(token)` |
| Injection | Automatic via reflection | Explicit via `module.Get[T](r, token)` |
| Lifecycle | `OnStart`/`OnStop` hooks | Manual cleanup |
| Module system | Groups (no visibility) | Full visibility enforcement |
| Type safety | Runtime type matching | Compile-time (explicit casts) |
Expand Down Expand Up @@ -127,7 +127,7 @@ svc := do.MustInvoke[UserService](injector)

```go
app, _ := kernel.Bootstrap(&AppModule{})
svc, _ := app.Get("users.service")
svc, _ := module.Get[UserService](app, "users.service")
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### Key Differences
Expand Down Expand Up @@ -211,7 +211,7 @@ modkit is directly inspired by [NestJS](https://nestjs.com/), bringing similar c
| `@Module()` decorator | `ModuleDef` struct |
| `@Injectable()` | `ProviderDef` |
| `@Controller()` | `ControllerDef` |
| Constructor injection | `r.Get(token)` |
| Constructor injection | `module.Get[T](r, token)` |
| `imports` | `Imports` |
| `providers` | `Providers` |
| `exports` | `Exports` |
Expand Down
4 changes: 2 additions & 2 deletions docs/guides/controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ func (m *UsersModule) Definition() module.ModuleDef {
Controllers: []module.ControllerDef{{
Name: "UsersController",
Build: func(r module.Resolver) (any, error) {
svc, err := r.Get(TokenUsersService)
svc, err := module.Get[UsersService](r, TokenUsersService)
if err != nil {
return nil, err
}
return NewUsersController(svc.(UsersService)), nil
return NewUsersController(svc), nil
},
}},
}
Expand Down
4 changes: 2 additions & 2 deletions docs/guides/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ func (m *AppModule) Definition() module.ModuleDef {
{
Name: "GreetingController",
Build: func(r module.Resolver) (any, error) {
value, err := r.Get(TokenGreeting)
value, err := module.Get[string](r, TokenGreeting)
if err != nil {
return nil, err
}
return &GreetingController{greeting: value.(string)}, nil
return &GreetingController{greeting: value}, nil
},
},
},
Expand Down
25 changes: 12 additions & 13 deletions docs/guides/lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,13 @@ Once built, providers are cached as singletons:

```go
// First call: builds the provider
svc1, _ := r.Get("users.service")
svc, err := module.Get[UserService](r, "users.service")

// Second call: returns cached instance
svc2, _ := r.Get("users.service")
svc2, _ := module.Get[UserService](r, "users.service")

// svc1 and svc2 are the same instance
fmt.Println(svc1 == svc2) // true
// svc and svc2 are the same instance
fmt.Println(svc == svc2) // true
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```

**Why singletons?**
Expand Down Expand Up @@ -189,11 +189,10 @@ func main() {
}

// Get resources that need cleanup
db, _ := app.Get("db.connection")
sqlDB := db.(*sql.DB)
db, _ := module.Get[*sql.DB](app, "db.connection")

// Defer cleanup
defer sqlDB.Close()
defer db.Close()

// Start server
router := mkhttp.NewRouter()
Expand Down Expand Up @@ -235,22 +234,22 @@ func (c *Cleanup) Run() error {
return &Cleanup{}, nil
}},
{Token: "db", Build: func(r module.Resolver) (any, error) {
cleanup, _ := r.Get("cleanup")
cleanup, _ := module.Get[*Cleanup](r, "cleanup")

db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}

cleanup.(*Cleanup).Register(db.Close)
cleanup.Register(db.Close)
return db, nil
}},

// In main
func main() {
app, _ := kernel.Bootstrap(&AppModule{})
cleanup, _ := app.Get("cleanup")
defer cleanup.(*Cleanup).Run()
cleanup, _ := module.Get[*Cleanup](app, "cleanup")
defer cleanup.Run()
// ...
}
```
Expand Down Expand Up @@ -437,8 +436,8 @@ func main() {
app, _ := kernel.Bootstrap(&AppModule{})

// Get DB for cleanup
db, _ := app.Get("db.connection")
defer db.(*sql.DB).Close()
db, _ := module.Get[*sql.DB](app, "db.connection")
defer db.Close()

// Start server (DB connection created on first query)
router := mkhttp.NewRouter()
Expand Down
8 changes: 4 additions & 4 deletions docs/guides/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,8 @@ func (m *AppModule) Definition() module.ModuleDef {
{
Token: "middleware.auth",
Build: func(r module.Resolver) (any, error) {
userSvc, _ := r.Get(TokenUserService)
return AuthMiddleware(userSvc.(UserService).ValidateToken), nil
userSvc, _ := module.Get[UserService](r, TokenUserService)
return AuthMiddleware(userSvc.ValidateToken), nil
},
},
},
Expand All @@ -286,8 +286,8 @@ Then retrieve and apply in your startup code:
```go
app, _ := kernel.Bootstrap(&AppModule{})

authMW, _ := app.Get("middleware.auth")
router.Use(authMW.(func(http.Handler) http.Handler))
authMW, _ := module.Get[func(http.Handler) http.Handler](app, "middleware.auth")
router.Use(authMW)
```

## Tips
Expand Down
18 changes: 7 additions & 11 deletions docs/guides/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,11 @@ Example:
module.ControllerDef{
Name: "UsersController",
Build: func(r module.Resolver) (any, error) {
dbAny, err := r.Get(TokenDB)
db, err := module.Get[*sql.DB](r, TokenDB)
if err != nil {
return nil, err
}
return NewUsersController(dbAny.(*sql.DB)), nil
return NewUsersController(db), nil
},
}
```
Expand Down Expand Up @@ -196,14 +196,10 @@ func (m *UsersModule) Definition() module.ModuleDef {
Providers: []module.ProviderDef{{
Token: TokenUsersService,
Build: func(r module.Resolver) (any, error) {
dbAny, err := r.Get(TokenDB)
db, err := module.Get[*sql.DB](r, TokenDB)
if err != nil {
return nil, err
}
db, ok := dbAny.(*sql.DB)
if !ok {
return nil, fmt.Errorf("expected *sql.DB for %q", TokenDB)
}
return NewUsersService(db), nil
},
}},
Expand Down Expand Up @@ -289,15 +285,15 @@ func (m *UsersModule) Definition() module.ModuleDef {
Providers: []module.ProviderDef{{
Token: TokenUsersService,
Build: func(r module.Resolver) (any, error) {
db, _ := r.Get(TokenDB)
return NewUsersService(db.(*sql.DB)), nil
db, _ := module.Get[*sql.DB](r, TokenDB)
return NewUsersService(db), nil
},
}},
Controllers: []module.ControllerDef{{
Name: "UsersController",
Build: func(r module.Resolver) (any, error) {
svc, _ := r.Get(TokenUsersService)
return NewUsersController(svc.(UsersService)), nil
svc, _ := module.Get[UsersService](r, TokenUsersService)
return NewUsersController(svc), nil
},
}},
Exports: []module.Token{TokenUsersService},
Expand Down
6 changes: 1 addition & 5 deletions docs/guides/nestjs-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,10 @@ module.ProviderDef{
Token: "users.reader",
// Note: requires fmt for error formatting.
Build: func(r module.Resolver) (any, error) {
v, err := r.Get("users.service")
svc, err := module.Get[*UsersService](r, "users.service")
if err != nil {
return nil, err
}
svc, ok := v.(*UsersService)
if !ok {
return nil, fmt.Errorf("users.service: expected *UsersService, got %T", v)
}
return svc, nil
},
}
Expand Down
10 changes: 4 additions & 6 deletions docs/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,16 @@ module.ProviderDef{
Token: TokenUsersService,
Build: func(r module.Resolver) (any, error) {
// Get a dependency
dbAny, err := r.Get(TokenDB)
db, err := module.Get[*sql.DB](r, TokenDB)
if err != nil {
return nil, err
}
db := dbAny.(*sql.DB)

// Get another dependency
loggerAny, err := r.Get(TokenLogger)
logger, err := module.Get[Logger](r, TokenLogger)
if err != nil {
return nil, err
}
logger := loggerAny.(Logger)

return NewUsersService(db, logger), nil
},
Expand Down Expand Up @@ -183,8 +181,8 @@ type MySQLUserRepository struct {

// Provider returns interface type
Build: func(r module.Resolver) (any, error) {
db, _ := r.Get(TokenDB)
return &MySQLUserRepository{db: db.(*sql.DB)}, nil
db, _ := module.Get[*sql.DB](r, TokenDB)
return &MySQLUserRepository{db: db}, nil
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```

Expand Down
36 changes: 12 additions & 24 deletions docs/reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,37 +91,25 @@ type Resolver interface {

Used in `Build` functions to retrieve dependencies.

---

## kernel

### Bootstrap
### Get[T] (Generic Helper)

```go
func Bootstrap(root Module) (*App, error)
func Get[T any](r Resolver, token Token) (T, error)
```

Entry point for bootstrapping your application. Returns an `App` with built controllers and a container for accessing providers.

### App

```go
type App struct {
Controllers map[string]any
}
```

| Field | Description |
|-------|-------------|
| `Controllers` | Map of controller key (`module:controller`) → controller instance |
Type-safe wrapper around `Resolver.Get`. Returns an error if resolution fails or if the type doesn't match `T`.

### App.Get

```go
func (a *App) Get(token Token) (any, error)
```

Resolves a token from the root module scope.
Resolves a token from the root module scope. Note: `module.Get[T]` works with `App` as well, as `App` implements a `Get` method compatible with the resolver pattern (though it returns the App struct itself which has a Get method).

Wait, `module.Get[T]` takes a `Resolver` interface. `App` has a `Get` method, so it implicitly satisfies `Resolver`.

Let's check `App` struct in `modkit/kernel/container.go`.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
### App.Resolver

Expand Down Expand Up @@ -257,15 +245,15 @@ func (m *UsersModule) Definition() module.ModuleDef {
Providers: []module.ProviderDef{{
Token: "users.service",
Build: func(r module.Resolver) (any, error) {
db, _ := r.Get("db.connection")
return NewUsersService(db.(*sql.DB)), nil
db, _ := module.Get[*sql.DB](r, "db.connection")
return NewUsersService(db), nil
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}},
Controllers: []module.ControllerDef{{
Name: "UsersController",
Build: func(r module.Resolver) (any, error) {
svc, _ := r.Get("users.service")
return NewUsersController(svc.(UsersService)), nil
svc, _ := module.Get[UsersService](r, "users.service")
return NewUsersController(svc), nil
},
}},
Exports: []module.Token{"users.service"},
Expand Down
Loading