Skip to content
/ melange Public

OpenFGA-to-PostgreSQL authorization compiler. Generates specialized SQL functions from .fga schemas that query your existing tables. Zero sync, always consistent.

License

Notifications You must be signed in to change notification settings

pthm/melange

Repository files navigation

Melange

GitHub License GitHub Release Go Reference Go Report Card

An OpenFGA-to-PostgreSQL authorization compiler.

Melange compiles OpenFGA authorization schemas into specialized PL/pgSQL functions that run directly in your PostgreSQL database. Like Protocol Buffers compiles .proto files into serialization code, Melange compiles .fga files into optimized SQL functions for Zanzibar-style relationship-based access control.

The generated functions query a melange_tuples view you define over your existing domain tables—no separate tuple storage or synchronization required.

Why Melange?

Traditional authorization systems require syncing your application data to a separate service. Melange takes a different approach: it's a compiler, not a runtime service.

How it works

Compile time — When you run melange migrate, the compiler:

  • Parses your OpenFGA schema
  • Analyzes relation patterns (direct, union, exclusion, etc.)
  • Computes transitive closures for role hierarchies
  • Generates specialized SQL functions for each relation
  • Installs the functions into PostgreSQL

Runtime — Permission checks are simple SQL function calls:

  • check_permission() executes the generated functions
  • Functions query a melange_tuples view you define over your domain tables
  • PostgreSQL's query planner optimizes the specialized functions

This compilation model gives you:

  • Always in sync — Permissions query your tables directly, no replication lag
  • Transaction-aware — Permission checks see uncommitted changes in the same transaction
  • Language-agnostic — Use from any language that can call SQL (Go, TypeScript, Python, Ruby, etc.)
  • Optional runtime libraries — Convenience clients for Go and TypeScript, or use raw SQL
  • Single query — Role hierarchies resolved at compile time, no recursive lookups at runtime

Inspired by OpenFGA and built on ideas from pgFGA.


Note

📚 Full Documentation Visit melange.sh for comprehensive guides, API reference, and examples.


Installation

CLI

Homebrew (macOS and Linux):

brew install pthm/tap/melange

Go install:

go install github.com/pthm/melange/cmd/melange@latest

Pre-built binaries: Download from GitHub Releases (macOS binaries are code-signed)

Updating:

# Homebrew
brew upgrade melange

# Go install
go install github.com/pthm/melange/cmd/melange@latest

Melange automatically checks for updates and notifies you when a new version is available. Use --no-update-check to disable.

Optional: Go Runtime Library

If you want to use the Go convenience library instead of raw SQL:

go get github.com/pthm/melange/melange

The runtime module has zero external dependencies (Go stdlib only).

Quick Start

1. Define Your Schema

Create a schema file (schema.fga) using the OpenFGA DSL:

model
  schema 1.1

type user

type repository
  relations
    define owner: [user]
    define reader: [user] or owner
    define can_read: reader

2. Compile Schema into SQL Functions

Run the migration to generate specialized PL/pgSQL functions:

melange migrate --db postgres://localhost/mydb --schemas-dir ./schemas/

This generates optimized SQL functions like check_permission(), list_objects(), and specialized check functions for each relation.

3. Define Your Tuples View

Create a melange_tuples view that exposes your authorization data:

CREATE VIEW melange_tuples AS
SELECT
  'user' AS subject_type,
  user_id::text AS subject_id,
  'owner' AS relation,
  'repository' AS object_type,
  repo_id::text AS object_id
FROM repository_owners
UNION ALL
SELECT 'user', user_id::text, 'reader', 'repository', repo_id::text
FROM repository_readers;

4. Check Permissions

With Go runtime (optional):

import "github.com/pthm/melange/melange"

checker := melange.NewChecker(db)
decision, err := checker.Check(ctx,
    melange.Object{Type: "user", ID: "alice"},
    "can_read",
    melange.Object{Type: "repository", ID: "my-repo"},
)
if !decision.Allowed {
    return ErrForbidden
}

Or use raw SQL from any language:

SELECT check_permission(
  'user', 'alice',
  'can_read',
  'repository', 'my-repo'
);
-- Returns: true/false

5. (Optional) Generate Type-Safe Client Code

For better type safety, generate constants and constructors:

melange generate client --runtime go --schema schema.fga --output ./authz/
import "yourapp/authz"

checker := melange.NewChecker(db)
decision, err := checker.Check(ctx,
    authz.User("alice"),
    authz.RelCanRead,
    authz.Repository("my-repo"),
)

CLI Reference

melange - PostgreSQL Fine-Grained Authorization

Commands:
  generate client  Generate type-safe client code from schema
  migrate          Apply schema to database
  validate         Validate schema syntax
  status           Show current schema status
  doctor           Run health checks on authorization infrastructure

Generate Client Code

# Generate Go code
melange generate client --runtime go --schema schema.fga --output ./authz/

# With custom package name
melange generate client --runtime go --schema schema.fga --output ./authz/ --package myauthz

# With int64 IDs instead of strings
melange generate client --runtime go --schema schema.fga --output ./authz/ --id-type int64

# Only generate permission relations (can_*)
melange generate client --runtime go --schema schema.fga --output ./authz/ --filter can_

Supported runtimes: go (TypeScript coming soon)

Apply Schema to Database

# Apply schema
melange migrate --db postgres://localhost/mydb --schemas-dir ./schemas/

# Dry run (show SQL without applying)
melange migrate --db postgres://localhost/mydb --schemas-dir ./schemas/ --dry-run

# Force re-apply even if unchanged
melange migrate --db postgres://localhost/mydb --schemas-dir ./schemas/ --force

Validate Schema

melange validate --schema schema.fga

Check Status

melange status --db postgres://localhost/mydb

Health Check

melange doctor --db postgres://localhost/mydb --verbose

Using from Any Language

Melange generates standard PostgreSQL functions, so you can use it from any language that can execute SQL:

# Python
cursor.execute(
    "SELECT check_permission(%s, %s, %s, %s, %s)",
    ('user', 'alice', 'can_read', 'repository', 'my-repo')
)
# Ruby
DB.fetch(
  "SELECT check_permission(?, ?, ?, ?, ?)",
  'user', 'alice', 'can_read', 'repository', 'my-repo'
).first
// TypeScript (with pg or any SQL client)
const result = await db.query("SELECT check_permission($1, $2, $3, $4, $5)", [
  "user",
  "alice",
  "can_read",
  "repository",
  "my-repo",
]);

Optional Runtime Libraries

For convenience, Melange provides type-safe runtime clients:

Language Runtime Package CLI Flag Status
Go github.com/pthm/melange/melange --runtime go Implemented
TypeScript @pthm/melange --runtime typescript Planned

These libraries provide a nicer API but are completely optional. See clients/ for language-specific implementations.


Contributing

Contributions are welcome! Here's how to get started:

  1. Fork the repository and clone locally
  2. Create a branch for your changes
  3. Run tests with just test
  4. Submit a pull request with a clear description

Please ensure your code:

  • Passes all existing tests
  • Includes tests for new functionality
  • Follows the existing code style

For bug reports and feature requests, please open an issue.


Resources

  • Documentation — Guides, API reference, and examples
  • OpenFGA — The authorization model Melange implements
  • Zanzibar Paper — Google's original authorization system
  • pgFGA — PostgreSQL FGA implementation that inspired this project

License

MIT License — see LICENSE for details.

About

OpenFGA-to-PostgreSQL authorization compiler. Generates specialized SQL functions from .fga schemas that query your existing tables. Zero sync, always consistent.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •