Project Structure
This guide explains Melange’s codebase layout and the purpose of each component.
Module Structure
Melange is organized as multiple Go modules for clean dependency isolation:
melange/
├── go.mod # Root module (github.com/pthm/melange)
├── melange/
│ └── go.mod # Runtime module (github.com/pthm/melange/melange)
├── clients/
│ └── typescript/ # TypeScript client (future)
├── pkg/ # Public packages
├── internal/ # Internal packages
├── cmd/melange/ # CLI (part of root module)
└── test/ # Tests (part of root module)| Module | Purpose | Dependencies |
|---|---|---|
Root (github.com/pthm/melange) | CLI, schema parsing, code generation | OpenFGA parser, pq driver |
Runtime (github.com/pthm/melange/melange) | Checker, cache, types, errors | stdlib only |
Runtime Module (melange/)
The runtime module has zero external dependencies and provides the permission checking API:
melange/
├── go.mod
├── melange.go # Object, Relation, Querier interfaces
├── checker.go # Checker implementation
├── cache.go # Permission result caching
├── decision.go # Decision overrides for testing
├── errors.go # Sentinel errors and helpers
└── validator.go # Input validationKey Files
melange.go - Core types:
Object- Type + ID resource identifierObjectLike/SubjectLike- Interfaces for domain modelsRelation/RelationLike- Relation typesQuerier- Interface for DB, Tx, Conn
checker.go - Permission checking:
Checker- Main API for permission checksCheck()- Single permission checkListObjects()/ListSubjects()- List operationsMust()- Panic on failure
cache.go - Caching:
CacheinterfaceCacheImpl- In-memory implementation with TTL
decision.go - Testing utilities:
Decisiontype (Allow/Deny/Unset)- Context-based decision propagation
Root Module Packages
Public Packages (pkg/)
pkg/
├── schema/ # Schema types, validation, closure computation
├── parser/ # OpenFGA DSL parsing
├── migrator/ # Database migration APIs
├── compiler/ # SQL code generation APIs
└── clientgen/ # Client code generation APIspkg/schema - Schema handling:
TypeDefinition- Object type with relationsRelationDefinition- Individual relation rulesAuthzModel- Database row representation- Validation and cycle detection
pkg/parser - Schema parsing:
ParseSchema(path)- Parse.fgafileParseSchemaString(dsl)- Parse DSL string- Wraps the official OpenFGA language parser
- Converts OpenFGA AST to
schema.TypeDefinitionslice - Only package that imports the OpenFGA parser dependency
pkg/migrator - Migration:
Migrate(ctx, db, dir)- One-step migrationMigrateFromString(ctx, db, dsl)- Migrate from string
pkg/clientgen - Client code generation:
Generate(runtime, types, cfg)- Generate client codeListRuntimes()- Available generators
Internal Packages (internal/)
internal/
├── clientgen/ # Generator registry and implementations
│ ├── generator.go # Generator interface
│ ├── go/ # Go generator
│ └── typescript/ # TypeScript generator (stub)
├── sqlgen/ # SQL DSL, query builders, code generation internals
└── doctor/ # CLI health check logicSQL Generation
SQL is generated from the internal sqlgen package:
internal/sqlgen/
├── sql.go # SQL DSL core
├── expr.go # Expression types
├── query.go # Query builders
├── check_queries.go # Permission check queries
├── list_queries.go # List operation queries
└── *.go # Additional helpersKey Functions
check_permission() - Core permission check:
- Takes subject, relation, object
- Returns 1 (allowed) or 0 (denied)
- Handles direct tuples, implied relations, parent inheritance, exclusions
list_accessible_objects() - Find accessible objects:
- Returns object IDs the subject can access
- Uses recursive CTE
list_accessible_subjects() - Find subjects with access:
- Returns subject IDs that have access to object
- Uses recursive CTE
CLI
The CLI is part of the root module:
cmd/melange/
└── main.goCommands:
validate- Check schema syntaxgenerate client- Generate type-safe client codemigrate- Apply schema to databasestatus- Check migration statusdoctor- Health check
Test Module
Tests are part of the root module:
test/
├── benchmark_test.go # Performance benchmarks
├── integration_test.go # Integration tests
├── openfgatests/ # OpenFGA compatibility suite
│ ├── loader.go # Test case loader
│ ├── client.go # Test client
│ ├── runner.go # Test execution
│ └── *_test.go # Test files
├── cmd/dumptest/ # Test case inspector
├── cmd/dumpsql/ # SQL dump tool
└── testutil/ # Test utilities
└── testdata/ # Test schemasOpenFGA Test Suite
The openfgatests package runs OpenFGA’s official test suite against Melange:
loader.go - Loads test cases from embedded YAML files
client.go - Test client:
- Uses
pkg/parserfor schema parsing - Creates database schema
- Executes assertions
runner.go - Test execution:
- Category-based test runners
- Pattern matching
- Benchmark wrappers
Language Clients
clients/
├── README.md # Overview and contribution guide
└── typescript/ # TypeScript client
├── package.json
├── tsconfig.json
└── src/The Go runtime lives at melange/ for clean imports:
import "github.com/pthm/melange/melange"Other language clients live under clients/ as their package managers don’t have Go’s path constraints.
Documentation
docs/
├── hugo.yaml # Hugo configuration
├── content/
│ ├── _index.md # Landing page
│ └── docs/ # Documentation pages
├── layouts/ # Custom layouts
├── assets/ # CSS/JS assets
└── themes/hextra/ # Hugo theme (submodule)Making Changes
Adding a Feature
- Runtime changes go in
melange/ - Parser changes go in
pkg/parser/ - SQL generation changes go in
internal/sqlgen/ - Tests go in
test/or the appropriate*_test.gofile
SQL Generation Changes
When modifying SQL generation in internal/sqlgen/:
- Update the query builder
- Run
just test-openfgato verify compatibility - Run
just bench-openfgato check performance impact
Adding a Language Generator
- Create
internal/clientgen/<language>/generate.go - Implement the
Generatorinterface - Register in
init()function - Import in
pkg/clientgen/api.go
Adding Tests
For OpenFGA compatibility tests, the test cases come from the embedded OpenFGA test suite. To test custom scenarios:
func TestCustomScenario(t *testing.T) {
db := testutil.SetupDB(t)
// Apply schema
err := migrator.MigrateFromString(ctx, db, `
model
schema 1.1
type user
type doc
relations
define owner: [user]
`)
require.NoError(t, err)
// Create tuples view
// Test assertions
}