Zero-dependency struct introspection for Go.
Extract struct metadata once, cache it permanently, and discover relationships between types.
type User struct {
ID string `json:"id" db:"id" validate:"required"`
Email string `json:"email" validate:"required,email"`
Profile *Profile
Orders []Order
}
metadata := sentinel.Scan[User]()
// metadata.TypeName → "User"
// metadata.FQDN → "github.com/app/models.User"
// metadata.Fields → []FieldMetadata (4 fields)
// metadata.Relationships → []TypeRelationship (2 relationships)
field := metadata.Fields[0]
// field.Name → "ID"
// field.Type → "string"
// field.Kind → "scalar"
// field.Tags → {"json": "id", "db": "id", "validate": "required"}
// field.Index → []int{0}One call extracts metadata for User and every type it touches — Profile, Order, and anything they reference. All cached permanently.
types := sentinel.Browse()
// [
// "github.com/app/models.User",
// "github.com/app/models.Profile",
// "github.com/app/models.Order",
// ]
relationships := sentinel.GetRelationships[User]()
// []TypeRelationship (2 relationships)
rel := relationships[0]
// rel.From → "github.com/app/models.User"
// rel.To → "github.com/app/models.Profile"
// rel.Field → "Profile"
// rel.Kind → "reference"
// rel.ToPackage → "github.com/app/models"Types don't change at runtime. Neither does their metadata.
go get github.com/zoobz-io/sentinel@latestRequires Go 1.24+.
package main
import (
"fmt"
"github.com/zoobz-io/sentinel"
)
type Order struct {
ID string `json:"id" db:"order_id" validate:"required"`
Total float64 `json:"total" validate:"gte=0"`
Status string `json:"status"`
}
type User struct {
ID string `json:"id" db:"user_id"`
Email string `json:"email" validate:"required,email"`
Orders []Order
}
func main() {
// Scan extracts User, Order, and their relationship
metadata := sentinel.Scan[User]()
// Type information
fmt.Println(metadata.TypeName) // "User"
fmt.Println(metadata.FQDN) // "main.User" (reflects actual package path)
// Field metadata
for _, field := range metadata.Fields {
fmt.Printf("%s (%s): %v\n", field.Name, field.Kind, field.Tags)
}
// ID (scalar): map[json:id db:user_id]
// Email (scalar): map[json:email validate:required,email]
// Orders (slice): map[]
// Relationships
for _, rel := range metadata.Relationships {
fmt.Printf("%s → %s (%s)\n", metadata.TypeName, rel.To, rel.Kind)
}
// User → main.Order (collection)
// Everything is cached
fmt.Println(sentinel.Browse())
// [main.User main.Order]
// Export the full schema
schema := sentinel.Schema()
fmt.Printf("%d types cached\n", len(schema))
}| Feature | Description | Docs |
|---|---|---|
| Metadata Extraction | Fields, types, indices, categories, struct tags | Concepts |
| Relationship Discovery | References, collections, embeddings, maps | Scanning |
| Permanent Caching | Extract once, cached forever | Architecture |
| Custom Tags | Register additional struct tags | Tags |
| Module-Aware Scanning | Recursive extraction within module boundaries | Scanning |
| Schema Export | Schema() returns all cached metadata |
API |
- Zero dependencies — only the Go standard library
- Permanent caching — types are immutable at runtime, so metadata is cached once
- Type-safe generics —
Inspect[T]()catches type errors at compile time - Relationship discovery — automatically maps references, collections, embeddings, and maps
- Module-aware scanning —
Scan[T]()recursively extracts related types within your module - Thread-safe — concurrent access after initial extraction
Sentinel metadata enables a pattern: define types once, generate everything else.
Your struct definitions become the single source of truth. Downstream tools consume sentinel's metadata to generate:
- Entity diagrams — Visualize domain models directly from type relationships
- Database schemas — Generate DDL and type-safe queries from struct tags
- API documentation — Produce OpenAPI specs from request/response types
- Learn
- Overview — purpose and motivation
- Quickstart — get started in 5 minutes
- Concepts — metadata, relationships, caching
- Architecture — internal design and components
- Guides
- Scanning — Inspect vs Scan, module boundaries
- Tags — custom tag registration
- Testing — testing with sentinel
- Troubleshooting — common issues and solutions
- Integrations
- Reference
See CONTRIBUTING.md for guidelines.
MIT License — see LICENSE for details.