Connor McCutcheon
/ Skykit
CLAUDE.md
md
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
**Skykit** is a lightweight web framework for building The Skyscape microservices. It provides MVC patterns, database management via LibSQL, OAuth authentication, and HTMX-friendly utilities.
## Development Commands
```bash
# Run tests
go test ./...
# Run single test
go test -run TestFunctionName ./...
# Run example app
cd example && go run .
```
## Architecture
### Core Components
| File | Purpose |
|------|---------|
| `application.go` | Application bootstrap, template rendering, server startup |
| `controller.go` | Base controller with HTMX helpers (`Refresh`, `Redirect`, `IsHTMX`) |
| `model.go` | Base model with ID, CreatedAt, UpdatedAt fields |
| `collection.go` | Generic CRUD operations for entities (Get, Insert, Update, Delete, Search, First) |
| `database.go` | LibSQL connection, reflection-based schema migration, cursor iteration |
| `view.go` | View serving with optional access checks |
| `authentication.go` | OAuth integration with The Skyscape platform |
| `option.go` | Functional options for application configuration |
### Application Entry Point Pattern
```go
//go:embed all:views
var views embed.FS
func main() {
    skykit.Serve(views,
        skykit.WithController(controllers.MyController()),
    )
}
```
### Controller Pattern
```go
// Factory function returns (name, handler)
func MyController() (string, skykit.Handler) {
    return "mycontroller", &MyController{}
}
type MyController struct {
    skykit.Controller  // Embed base controller
}
func (c *MyController) Setup(app *skykit.Application) {
    c.Controller.Setup(app)  // Always call parent
    http.Handle("GET /", c.Serve("home.html", nil))
    http.HandleFunc("POST /items", c.handleCreate)
}
// VALUE receiver creates copy for request isolation
func (c MyController) Handle(r *http.Request) skykit.Handler {
    c.Request = r
    return &c
}
// Public methods are accessible in templates: {{mycontroller.AllItems}}
func (c *MyController) AllItems() []*Item { /* ... */ }
```
### Model Pattern
```go
type Item struct {
    skykit.Model  // Embeds ID, CreatedAt, UpdatedAt
    Name  string
    Count int
}
func (i *Item) GetModel() *skykit.Model {
    return &i.Model
}
```
### Database Pattern
```go
var (
    DB    = skykit.ConnectDB()
    Items = skykit.Manage(DB, "items", new(Item))
)
func init() {
    go Items.UniqueIndex("Name")  // Background index creation
}
```
### Collection Methods
- `New()` - Create new entity instance
- `Get(id)` - Get by ID
- `First(query, args...)` - Get first matching record
- `Search(query, args...)` - Get all matching records
- `Insert(entity)` - Insert new record (auto-generates UUID if ID empty)
- `Update(entity)` - Update existing record
- `Delete(entity)` - Delete record
- `Count(query, args...)` - Count matching records
- `Index(columns...)` / `UniqueIndex(columns...)` - Create indexes
### HTMX Response Helpers
```go
c.Refresh(w, r)           // HX-Refresh: true or redirect to same page
c.Redirect(w, r, "/path") // HX-Location or HTTP redirect
c.IsHTMX(r)               // Check if request is from HTMX
```
## Key Patterns
1. **IDs are ALWAYS strings** - UUIDs generated automatically
2. **SQL uses PascalCase** - `WHERE UserID = ?` not `WHERE user_id = ?`
3. **Templates reference by filename only** - `{{template "card.html" .}}`
4. **Controllers accessible in templates** - `{{mycontroller.Method}}`
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `DB_NAME` | For replica | Local database filename |
| `DB_URL` | For replica | LibSQL primary URL |
| `DB_TOKEN` | For replica | Database JWT token |
| `PORT` | No | Server port (default: 5000) |
| `APP_ID` | OAuth | OAuth client ID |
| `OAUTH_CLIENT_SECRET` | OAuth | OAuth client secret |
| `SKYSCAPE_HOST` | No | OAuth provider (default: https://theskyscape.com) |
## Database
- Uses LibSQL with embedded replica pattern when `DB_NAME`, `DB_URL`, and `DB_TOKEN` are set
- Falls back to in-memory SQLite when environment variables are missing
- Schema migrations happen automatically via reflection on first run
- Field types: TEXT (string), INTEGER (int), REAL (float), BOOLEAN (bool), TIMESTAMP (time.Time)
## Dependencies
- `github.com/tursodatabase/go-libsql` - LibSQL driver
- `github.com/google/uuid` - UUID generation
- `github.com/pkg/errors` - Error wrapping
No comments yet.