Skip to content

zoobz-io/sentinel

Repository files navigation

sentinel

CI Status codecov Go Report Card CodeQL Go Reference License Go Version Release

Zero-dependency struct introspection for Go.

Extract struct metadata once, cache it permanently, and discover relationships between types.

Structured Type Intelligence

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.

Install

go get github.com/zoobz-io/sentinel@latest

Requires Go 1.24+.

Quick Start

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))
}

Capabilities

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

Why sentinel?

  • Zero dependencies — only the Go standard library
  • Permanent caching — types are immutable at runtime, so metadata is cached once
  • Type-safe genericsInspect[T]() catches type errors at compile time
  • Relationship discovery — automatically maps references, collections, embeddings, and maps
  • Module-aware scanningScan[T]() recursively extracts related types within your module
  • Thread-safe — concurrent access after initial extraction

Type-Driven Generation

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

Documentation

  • Learn
  • Guides
  • Integrations
    • erd — entity relationship diagrams
    • soy — SQL injection-safe queries
    • rocco — OpenAPI generation
  • Reference
    • API — complete function documentation
    • Types — Metadata, FieldMetadata, TypeRelationship

Contributing

See CONTRIBUTING.md for guidelines.

License

MIT License — see LICENSE for details.